Compare commits

..

51 Commits

Author SHA1 Message Date
NewSoupVi
77e6fa0010 The Witness: Move the Easter Egg Hunt option group lower so that the tooltip isn't cut off 2025-04-01 21:09:59 +02:00
LiquidCat64
d26db6f213 CV64: Fix some unrandomized locations containing unintended items on specific settings (#4728)
* Fix some unrandomized locations on specific settings.

* Remove now-unnecessary comment
2025-04-01 12:37:49 -04:00
Fabian Dill
bb6c753583 FFMQ: fix remote code execution (#4786) 2025-04-01 18:19:07 +02:00
Mysteryem
ca08e4b950 Super Metroid: Replace random module with world random in variaRandomizer (#4429) 2025-04-01 18:14:47 +02:00
Bryce Wilson
5a6b02dbd3 Pokemon Emerald: Fix pre-fill problems (#4686)
Co-authored-by: Mysteryem <Mysteryem@users.noreply.github.com>
2025-04-01 18:12:43 +02:00
jamesbrq
14416b1050 MLSS: Fix issue with door opening earlier than intended (#4737) 2025-04-01 18:10:51 +02:00
Carter Hesterman
da4e6fc532 Civ6: Sanitize player/item values before they go in the XML (#4755) 2025-04-01 18:09:59 +02:00
Justus Lind
57d8b69a6d Muse Dash: Update Song List to Muse Dash Legend. (#4775)
* Add Muse Dash Legend songs.

* Add a new SFX trap
2025-04-01 18:08:09 +02:00
Silvris
c9d8a8661c kvui: Fix hint tab formatting regression (#4778)
Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
2025-04-01 18:06:49 +02:00
Fabian Dill
4a3d23e0e6 Core: update cx-Freeze to 8.0.0 & Worlds: fix packages missing __init__.py (#4773) 2025-04-01 16:29:32 +02:00
PoryGone
a3666f2ae5 SA2B: Fix critical typo #4779 2025-03-30 22:19:24 +02:00
Kaito Sinclaire
c3e000e574 id Tech 1 games: Logic updates (Feb '25) (#4677)
- Across Doom 1993 and Doom 2, any items that are accessible in Ultra-Violence from the start of the level without putting the player in any danger are now considered in logic when that level is first received, without needing any weapons available. This is intended to give generation more possible outs for bad placements.
  - This affects the following maps in Doom 1993:
    - Toxin Refinery (E1M3): 1 location.
    - Command Control (E1M4): 1 location.
    - Computer Station (E1M7): 1 location.
    - Deimos Lab (E2M4): 1 location.
    - Tower of Babel (E2M8): 1 location.
    - Unholy Cathedral (E3M5): 1 location.
  - This affects the following maps in Doom 2:
    - The Waste Tunnels (MAP05): 2 locations.
    - Dead Simple (MAP07): 2 locations.
    - The Pit (MAP09): 1 location.
    - Refueling Base (MAP10): 1 location.
    - Nirvana (MAP21): 1 location, except see below.
    - Icon of Sin (MAP30): 9 locations.
    - Grosse (MAP32): 2 locations.
- Doom 2 has had some more significant logical adjustments made.
  - The following Pro tricks have been added to Pro logic:
    - Circle of Death (MAP11): Lowering the exit wall without the Red key by hitting the switch to do so from the nukage. This makes three items previously locked behind the Red key available early, as well as the exit.
    - Suburbs (MAP16): Reaching the exit without any keys, as the gap between the pillar and the wall is large enough to let you through if you position yourself well. While multiple other squeeze glides exist (for example, you can skip the Yellow key in MAP21 by using one), this one is significantly easier than the rest; it does not require much precision, nor does it require vertical mouse movement.
    - Nirvana (MAP21): Skipping the Blue key, as there is a gigantic gap between the bars that attempt to block you.
    - The Chasm (MAP24): Skipping the Blue key by going extremely far through the nukage and finding one of a couple specific teleporters is now considered a Pro trick, and standard logic now expects the key to be obtained.
  - The following levels have had other logic adjustments:
    - The Waste Tunnels (MAP05): Requirements lowered to Shotgun + Super Shotgun + (Chaingun | Plasma gun).
    - The Crusher (MAP06): Requirements lowered to Shotgun + (Chaingun | Plasma gun) for areas immediately accessible. Going beyond the Blue key door also requires Super Shotgun.
    - The Factory (MAP12): The outdoors area, and the little room to the right of where you start, are accessible in sphere 1. These three items are all easily obtainable with only the pistol. The remaining items that are not in the central area are accessible with (Super Shotgun | Plasma gun), while the items in that area are accessible with Super Shotgun + Chaingun + (Plasma gun | BFG9000). This fixes Episode 2 not having an available sphere 1, and allows solo Episode 2 games.
    - Nirvana (MAP21): As above, the item in the starting room is accessible in sphere 1. Every other item that doesn't require a key is accessible with (Super Shotgun | Plasma gun). The room in which you use the Yellow key is accessible with Super Shotgun + Chaingun + (Plasma gun | BFG9000). This fixes Episode 3 not having an available sphere 1, and allows solo Episode 3 games.
    - The Catacombs (MAP22): The four items in the opening room only require (Shotgun | Super Shotgun | Plasma gun). The rest of the level is as before.
    - Bloodfalls (MAP25): Requirements lowered to Shotgun + Super Shotgun + Chaingun, as this level is unusually easy for its placement in the game. Progressing past the Blue key door additionally requires (Rocket launcher | Plasma gun | BFG9000) solely to deal with the Arch-vile at the end of the level.
    - Wolfenstein (MAP31): Requirements lowered to Chaingun + (Shotgun | Super Shotgun). This is closer to what the game expects from a non-secret hunting player from a pistol start.
- The following logic bugs in Heretic have been fixed:
  - Quay (E5M3): An item in a Blue key locked hallway was previously marked as being in the "Main" region, thus considered to be accessible without that key. It has been moved to the appropriate "Blue" region.
  - Courtyard (E5M4): Logic previously assumed you could reach the Wings of Wrath from the opening room, when that isn't actually possible. Changing this moved some items previously in the "Main" region into a new "Green" region, and items previously in the "Kakis" (Yellow OR Green) are now in a "Yellow" region instead. Fixes #4662.
- For known problematic solo episodes, some additional special cases have been added.
  - Doom 1993, Episode 3: One of either the Shotgun or Chaingun is placed early. Slough of Despair (E3M2) is given as an additional starting level.
  - Doom 2, Episode 3: One of either the Super Shotgun or Plasma gun is placed early.
  - Heretic, Episode 1: The Docks (E1M1) - Yellow key is placed early.
- The following levels (and thus, their items and locations) were renamed, due to typos or other oddities:
  - `Barrels o Fun (MAP23)` -> `Barrels o' Fun (MAP23)`
  - `Wolfenstein2 (MAP31)` -> `Wolfenstein (MAP31)`
  - `Grosse2 (MAP32)` -> `Grosse (MAP32)`
  - `D'Sparil'S Keep (E3M8)` -> `D'Sparil's Keep (E3M8)`
  - `The Aquifier (E3M9)` -> `The Aquifer (E3M9)`
2025-03-29 17:32:33 +01:00
Justus Lind
dd5481930a Muse Dash: Update docs to recommend MelonLoader 0.7.0 rather than 0.6.1 (#4776)
* Tiny version update.

* Update wording because there is no longer a latest button
2025-03-29 01:35:35 +01:00
Scipio Wright
842328c661 TUNIC: Update swamp and atoll fuse logic with weaponry (#4760)
* Update swamp and atoll fuse logic with weaponry

* Add it to the swamp and cath rules too
2025-03-28 21:12:16 +01:00
PoryGone
8f75384e2e SA2B - v2.4 Logic Fixes (#4770)
* Logic tweaks

* Docs updates

* Delete extra file

* One more logic tweak

* Add missing logic change
2025-03-28 21:11:31 +01:00
Fabian Dill
193faa00ce Factorio: fix energylink type back to int (#4768) 2025-03-28 00:28:10 +01:00
Star Rauchenberger
5e5383b399 Lingo: Add painting display names (#4707)
* Lingo: Add painting display names

* Reordered some paintings

* Update generated.dat
2025-03-27 01:32:39 +01:00
threeandthreee
cb6b29dbe3 LADX: fix for unconnected entrances in other worlds #4771 2025-03-25 22:30:25 +01:00
Fabian Dill
82b0819051 Core: ensure requirements files end on newline (#4761) 2025-03-24 22:26:30 +01:00
Jérémie Bolduc
e12ab4afa4 Stardew Valley: Move test option presets to their own file (#4349) 2025-03-24 03:32:34 +01:00
Justus Lind
1416f631cc Core: Add a test that checks all registered patches matches the name of a registered world (#4633)
Co-authored-by: qwint <qwint.42@gmail.com>
2025-03-24 03:30:44 +01:00
Fabian Dill
dbaac47d1e Core: update various requirements (#4731) 2025-03-23 17:24:50 +01:00
Jonathan Tan
cf0ae5e31b The Wind Waker: Implement New Game (#4458)
Adds The Legend of Zelda: The Wind Waker as a supported game in Archipelago. The game uses [LagoLunatic's randomizer](https://github.com/LagoLunatic/wwrando) as its base (regarding logic, options, etc.) and builds from there.
2025-03-23 00:42:17 +01:00
BadMagic100
8891f07362 Core: Allow and require user-provided target name when splitting 1-way entrances for GER (#4746)
* [Core][GER] Allow and require user-provided target name when splitting 1-way entrances

* Move target naming onto a parameter of disconnect_entrance_for_randomization
2025-03-22 20:58:35 +01:00
NewSoupVi
d78974ec59 The Witness: Bump Required Client Version to 0.6.0 (#4763)
The beta client releases already report this.
2025-03-22 20:57:22 +01:00
NewSoupVi
32be26c4d7 The Witness: Make sure the 2025 April Fools feature does not go live with RC3 (#4758) 2025-03-22 20:52:18 +01:00
Jérémie Bolduc
9de49aa419 Stardew Valley: Move all the goal logic into its own file (#4383) 2025-03-22 20:29:16 +01:00
PoryGone
294a67a4b4 SA2B: v2.4 - Minigame Madness (#4663)
Changelog:

Features:
- New Goal
  - Minigame Madness
    - Win a certain number of each type of Minigame Trap, then defeat the Finalhazard to win!
	- How many of each Minigame are required can be set by an Option
	- When the required amount of a Minigame has been received, that Minigame can be replayed in the Chao World Lobby
- New optional Location Checks
  - Bigsanity
    - Go fishing with Big in each stage for a Location Check
  - Itemboxsanity
    - Either Extra Life Boxes or All Item Boxes
- New Items
  - New Traps
    - Literature Trap
	- Controller Drift Trap
	- Poison Trap
	- Bee Trap
  - New Minigame Traps
    - Breakout Trap
	- Fishing Trap
	- Trivia Trap
	- Pokemon Trivia Trap
	- Pokemon Count Trap
	- Number Sequence Trap
	- Light Up Path Trap
	- Pinball Trap
	- Math Quiz Trap
	- Snake Trap
	- Input Sequence Trap
- Trap Link
  - When you receive a trap, you send a copy of it to every other player with Trap Link enabled
- Boss Gate Plando
- Expert Logic Difficulty
	- Use at your own risk. This difficulty requires complete mastery of SA2.
- Missions can now be enabled and disabled per-character, instead of just per-style
- Minigame Difficulty can now be set to "Chaos", which selects a new difficulty randomly per-trap received

Quality of Life:
- Gate Stages and Mission Orders are now displayed in the spoiler log
- Additional play stats are saved and displayed with the randomizer credits
- Stage Locations progress UI now displays in multiple pages when Itemboxsanity is enabled
- Current stage mission order and progress are now shown when paused in-level
- Chaos Emeralds are now shown when paused in-level
- Location Name Groups were created
- Moved SA2B to the new Options system
- Option Presets were created
- Error Messages are more obvious

Bug Fixes:
- Added missing `Dry Lagoon - 12 Animals` location
- Flying Dog boss should no longer crash when you have done at least 3 Intermediate Kart Races
- Invincibility can no longer be received in the King Boom Boo fight, preventing a crash
- Chaos Emeralds should no longer disproportionately end up in Cannon's Core or the final Level Gate
- Going into submenus from the pause menu should no longer reset traps
- `Sonic - Magic Gloves` are now plural
- Junk items will no longer cause a crash when in a falling state
- Chao Garden:
	- Prevent races from occasionally becoming uncompletable when using the "Prize Only" option
	- Properly allow Hero Chao to participate in Dark Races
	- Don't allow the Chao Garden to send locations when connected to an invalid server
	- Prevent the Chao Garden from resetting your life count
	- Fix Chao World Entrance Shuffle causing inaccessible Neutral Garden
	- Fix pressing the 'B' button to take you to the proper location in Chao World Entrance Shuffle
	- Prevent Chao Karate progress icon overflow
	- Prevent changing Chao Timescale while paused or while a Minigame is active
- Logic Fixes:
	- `Mission Street - Chao Key 1` (Hard Logic) now requires no upgrades
	- `Mission Street - Chao Key 2` (Hard Logic) now requires no upgrades
	- `Crazy Gadget - Hidden 1` (Standard Logic) now requires `Sonic - Bounce Bracelet` instead of `Sonic - Light Shoes`
	- `Lost Colony - Hidden 1` (Standard Logic) now requires `Eggman - Jet Engine`
	- `Mad Space - Gold Beetle` (Standard Logic) now only requires `Rouge - Iron Boots`
	- `Cosmic Wall - Gold Beetle` (Standard and Hard Logic) now only requires `Eggman - Jet Engine`
2025-03-22 13:00:07 +01:00
panicbit
0e99888926 LADX: Stop spamming location checks over network (#4757) 2025-03-21 17:10:17 +01:00
qwint
74cbf10930 Civ6: Use AutoPatchRegister to make patch downloadable on webhost #4752 2025-03-20 19:28:16 +01:00
BadMagic100
08d2909b0e Hollow Knight: Include Lumafly links to install mods in docs (#4745) 2025-03-20 11:49:55 -04:00
CaitSith2
0949b11436 ALttP: Don't crash generation if sprite paths don't exist (#4725) 2025-03-20 14:48:30 +01:00
Aaron Wagener
9cdffe7f63 The Messenger: Add display names to the plando options (#4748) 2025-03-19 15:52:14 -04:00
Bryce Wilson
8b2a883669 Pokemon Emerald: Update changelog (#4747) 2025-03-19 02:17:01 +01:00
NewSoupVi
b7fc96100c Revert "Core: update websockets (#4732)" (#4753)
This reverts commit 42eaeb92f0.
2025-03-19 01:39:18 +01:00
Aaron Wagener
63cbc00a40 The Messenger: Fix corrupted future rule (#4749) 2025-03-18 19:01:31 -04:00
CodeGorilla
57b94dba6f Options: Add a column for player ID to --csv_output (#4715) 2025-03-17 21:43:00 +01:00
ironminer888
0dd188e108 LADX: Add more specific "item icon guessing" support for some games (#4706)
* DKC3, PKMN R/B/Em, M&L specific item matches

* MLSS Bean types are now discrete

* Add Doom 1/2 items

* Add Doom 1/2 items, actually

* Add Inscryption items

* Add more SA2B items, Minecraft

* Add VVVVVV

* Add misc items, comma fixes

* Hat in Time items

* Misc changes

* Expand TODO

* Add more OoT items, Pokemon consumables

* KH2

* KH1, adjust KH2 items

* Formatting fixes

* more item changes, fix kh1 name

* Fix KH1 name

* Add Full Heal to MEDICINE graphics

* Final comma fixes before PR

* Add Full Restore as Medicine

* Move some names to generic, drink fixes, double-quotes consistency fix

* moved ROCK SMASH match to PHRASES dict

* Removed some redundant name checks, remove Old Amber check from Emerald

* Added "PASS" generic check as "LETTER" sprite

* Removed TODO

* Corrected KH1 name for real this time

* Icon assignment now uppers freogin item string during comparison

* Doom skull keys are now NIGHTMARE_KEY, added QUILL as generic for FEATHER

* KH2 armor is Blunic, accessories are Ribbons

* KH1 accessories/armor are Blunic

* "ROCK SMASH" is now "BOMB"

* Removed extra space
2025-03-17 11:50:57 -04:00
PoryGone
bf8c840293 Celeste 64: v1.3 Content Update (#4581)
### Features:

- New optional Location Checks
	- Checkpointsanity
- Hair Color
	- Allows for setting of Maddy's hair color in each of No Dash, One Dash, Two Dash, and Feather states
- Other Player Ghosts
	- A game config option allows you to see ghosts of other Celeste 64 players in the multiworld

### Quality of Life:

- Checkpoint Warping
	- Received Checkpoint items allow for warping to their respective checkpoint
		- These items are on their respective checkpoint location if Checkpointsanity is disabled
	- Logic accounts for being able to warp to otherwise inaccessible areas
	- Checkpoints are a possible option for a starting item on Standard Logic + Move Shuffle + Checkpointsanity
- New Options toggle to enable/disable background input

### Bug Fixes:

- Traffic Blocks now correctly appear disabled within Cassettes
2025-03-17 02:46:34 +01:00
black-sliver
c0244f3018 Tests: unroll 2 player gen, add parametrization helper, add docs (#4648)
* Tests: unroll test_multiworlds.TestTwoPlayerMulti

Also adds a helper function that other tests can use to unroll tests.

* Docs: add more details to docs/tests.md

* Explain parametrization, subtests and link to the new helper
* Mention some performance details and work-arounds
* Mention multithreading / pytest-xdist

* Tests: make param.classvar_matrix accept sets

* CI: add test/param.py to type checking

* Tests: add missing typing to test/param.py

* Tests: fix typo in test/param.py doc comment

Co-authored-by: qwint <qwint.42@gmail.com>

* update docs

* Docs: reword note on performance

---------

Co-authored-by: qwint <qwint.42@gmail.com>
2025-03-17 00:16:02 +01:00
black-sliver
8af8502202 CI: pin some actions (#4744) 2025-03-17 00:02:00 +01:00
Fabian Dill
42eaeb92f0 Core: update websockets (#4732) 2025-03-16 22:13:12 +01:00
Alchav
7f35eb8867 Pokémon R/B: Allow generating with all items linked (#4330)
* Pokémon R/B: Allow generating with all items linked

* check priority/excluded locations for pc_item

* Update regions.py

* Un-remove regions.py code
2025-03-16 12:33:24 -04:00
BadMagic100
785569c40c Core: Generic ER fails in stage 1 when the last available target is an indirect conditioned dead end (#4679)
* Add test that stage1 ER will not fail due to speculative sweeping an indirect conditioned dead end

* Skip speculative sweep if it's the last entrance placement

* Better implementation of needs_speculative_sweep

* pep8
2025-03-15 18:56:07 +01:00
Scipio Wright
a9eb70a881 OoT: Remove Outdated Spanish Setup Guide (#4736)
* Remove spanish setup guide from webworld

* Update __init__.py

* Update __init__.py
2025-03-15 07:16:06 -04:00
Scipio Wright
5d3d0c8625 WebHost: Update text for options you can't modify (#4614) 2025-03-15 07:10:07 -04:00
Scipio Wright
7e32feeea3 Webhost: Update random option wording on webhost (#4555)
* Update random option wording on webhost

* Update WebHostLib/templates/playerOptions/macros.html

Co-authored-by: Jouramie <16137441+Jouramie@users.noreply.github.com>
2025-03-15 07:09:04 -04:00
neocerber
0d1935e757 SC2: Add a description of mission order and the impact of collect on a SC2 world (#4398)
* Added mission order to randomized stuff, added a mention to the default option collect on goal, added an issue about mission order progress vs AP collect

* Remove false menion of collect being note modifyable after the mworld was gen

* Simplification of some sentences

* American spelling, header newline, and other

* Revert gray to grey, corrected some colors

* Forgot a gray -> grey

* Replace how the faction color option is described to side-step difference within yaml and client. Both fr/en.
2025-03-14 11:35:58 -04:00
Benny D
9b3ee018e9 Core/Various Worlds: Fix crash/freeze with unicode characters (#4671)
replace colorama.init with just_fix_windows_console
2025-03-14 08:24:37 +01:00
NewSoupVi
1de411ec89 The Witness: Change Regions, Areas and Connections from Dict[str, Any] to dataclasses&NamedTuples (#4415)
* Change Regions, Areas and Connections to dataclasses/NamedTuples

* Move to new file

* we do a little renaming

* Purge the 'lambda' naming in favor of 'rule' or 'WitnessRule'

* missed one

* unnecessary change

* omega oops

* NOOOOOOOO

* Merge error

* mypy thing
2025-03-13 23:59:09 +01:00
LiquidCat64
3192799bbf CVCotM: Clarify the Wii U VC version is unsupported (#4734)
* Comment out VC ROM hash usages and clarify that it's unsupported.

* Update worlds/cvcotm/docs/en_Castlevania - Circle of the Moon.md

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

* Update worlds/cvcotm/docs/setup_en.md

Co-authored-by: Scipio Wright <scipiowright@gmail.com>

---------

Co-authored-by: Scipio Wright <scipiowright@gmail.com>
2025-03-13 00:21:09 +01:00
203 changed files with 18973 additions and 4046 deletions

View File

@@ -2,6 +2,7 @@
"include": [ "include": [
"../BizHawkClient.py", "../BizHawkClient.py",
"../Patch.py", "../Patch.py",
"../test/param.py",
"../test/general/test_groups.py", "../test/general/test_groups.py",
"../test/general/test_helpers.py", "../test/general/test_helpers.py",
"../test/general/test_memory.py", "../test/general/test_memory.py",

View File

@@ -36,9 +36,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1 - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
if: startsWith(matrix.os,'windows') if: startsWith(matrix.os,'windows')
- uses: Bacondish2023/setup-googletest@v1 - uses: Bacondish2023/setup-googletest@49065d1f7a6d21f6134864dd65980fe5dbe06c73
with: with:
build-type: 'Release' build-type: 'Release'
- name: Build tests - name: Build tests

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@
*.apmc *.apmc
*.apz5 *.apz5
*.aptloz *.aptloz
*.aptww
*.apemerald *.apemerald
*.pyc *.pyc
*.pyd *.pyd

View File

@@ -511,7 +511,7 @@ if __name__ == '__main__':
import colorama import colorama
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -1128,7 +1128,7 @@ def run_as_textclient(*args):
args = handle_url_arg(args, parser=parser) args = handle_url_arg(args, parser=parser)
# use colorama to display colored text highlighting on windows # use colorama to display colored text highlighting on windows
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main(args)) asyncio.run(main(args))
colorama.deinit() colorama.deinit()

View File

@@ -261,7 +261,7 @@ if __name__ == '__main__':
parser = get_base_parser() parser = get_base_parser()
args = parser.parse_args() args = parser.parse_args()
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main(args)) asyncio.run(main(args))
colorama.deinit() colorama.deinit()

View File

@@ -506,7 +506,7 @@ class LinksAwakeningContext(CommonContext):
la_task = None la_task = None
client = None client = None
# TODO: does this need to re-read on reset? # TODO: does this need to re-read on reset?
found_checks = [] found_checks = set()
last_resend = time.time() last_resend = time.time()
magpie_enabled = False magpie_enabled = False
@@ -558,10 +558,6 @@ class LinksAwakeningContext(CommonContext):
self.ui = LADXManager(self) self.ui = LADXManager(self)
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
async def send_checks(self):
message = [{"cmd": "LocationChecks", "locations": self.found_checks}]
await self.send_msgs(message)
async def send_new_entrances(self, entrances: typing.Dict[str, str]): async def send_new_entrances(self, entrances: typing.Dict[str, str]):
# Store the entrances we find on the server for future sessions # Store the entrances we find on the server for future sessions
@@ -613,8 +609,8 @@ class LinksAwakeningContext(CommonContext):
self.client.pending_deathlink = True self.client.pending_deathlink = True
def new_checks(self, item_ids, ladxr_ids): def new_checks(self, item_ids, ladxr_ids):
self.found_checks += item_ids self.found_checks.update(item_ids)
create_task_log_exception(self.send_checks()) create_task_log_exception(self.check_locations(self.found_checks))
if self.magpie_enabled: if self.magpie_enabled:
create_task_log_exception(self.magpie.send_new_checks(ladxr_ids)) create_task_log_exception(self.magpie.send_new_checks(ladxr_ids))
@@ -721,7 +717,7 @@ class LinksAwakeningContext(CommonContext):
if self.last_resend + 5.0 < now: if self.last_resend + 5.0 < now:
self.last_resend = now self.last_resend = now
await self.send_checks() await self.check_locations(self.found_checks)
if self.magpie_enabled: if self.magpie_enabled:
try: try:
self.magpie.set_checks(self.client.tracker.all_checks) self.magpie.set_checks(self.client.tracker.all_checks)
@@ -803,6 +799,6 @@ async def main():
await ctx.shutdown() await ctx.shutdown()
if __name__ == '__main__': if __name__ == '__main__':
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -370,7 +370,7 @@ if __name__ == "__main__":
import colorama import colorama
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -47,7 +47,7 @@ from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, Networ
from BaseClasses import ItemClassification from BaseClasses import ItemClassification
min_client_version = Version(0, 1, 6) min_client_version = Version(0, 1, 6)
colorama.init() colorama.just_fix_windows_console()
def remove_from_list(container, value): def remove_from_list(container, value):

View File

@@ -346,7 +346,7 @@ if __name__ == '__main__':
import colorama import colorama
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -1579,6 +1579,7 @@ def dump_player_options(multiworld: MultiWorld) -> None:
player_output = { player_output = {
"Game": multiworld.game[player], "Game": multiworld.game[player],
"Name": multiworld.get_player_name(player), "Name": multiworld.get_player_name(player),
"ID": player,
} }
output.append(player_output) output.append(player_output)
for option_key, option in world.options_dataclass.type_hints.items(): for option_key, option in world.options_dataclass.type_hints.items():
@@ -1591,7 +1592,7 @@ def dump_player_options(multiworld: MultiWorld) -> None:
game_option_names.append(display_name) game_option_names.append(display_name)
with open(output_path(f"generate_{multiworld.seed_name}.csv"), mode="w", newline="") as file: with open(output_path(f"generate_{multiworld.seed_name}.csv"), mode="w", newline="") as file:
fields = ["Game", "Name", *all_option_names] fields = ["ID", "Game", "Name", *all_option_names]
writer = DictWriter(file, fields) writer = DictWriter(file, fields)
writer.writeheader() writer.writeheader()
writer.writerows(output) writer.writerows(output)

View File

@@ -81,6 +81,7 @@ Currently, the following games are supported:
* Castlevania: Circle of the Moon * Castlevania: Circle of the Moon
* Inscryption * Inscryption
* Civilization VI * Civilization VI
* The Legend of Zelda: The Wind Waker
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). 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 Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

View File

@@ -735,6 +735,6 @@ async def main() -> None:
if __name__ == '__main__': if __name__ == '__main__':
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -500,7 +500,7 @@ def main():
import colorama import colorama
colorama.init() colorama.just_fix_windows_console()
asyncio.run(_main()) asyncio.run(_main())
colorama.deinit() colorama.deinit()

View File

@@ -446,6 +446,6 @@ if __name__ == '__main__':
parser = get_base_parser(description="Wargroove Client, for text interfacing.") parser = get_base_parser(description="Wargroove Client, for text interfacing.")
args, rest = parser.parse_known_args() args, rest = parser.parse_known_args()
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main(args)) asyncio.run(main(args))
colorama.deinit() colorama.deinit()

View File

@@ -1,11 +1,11 @@
flask>=3.0.3 flask>=3.1.0
werkzeug>=3.0.6 werkzeug>=3.1.3
pony>=0.7.19 pony>=0.7.19
waitress>=3.0.0 waitress>=3.0.2
Flask-Caching>=2.3.0 Flask-Caching>=2.3.0
Flask-Compress>=1.15 Flask-Compress>=1.17
Flask-Limiter>=3.8.0 Flask-Limiter>=3.12
bokeh>=3.5.2 bokeh>=3.6.3
markupsafe>=2.1.5 markupsafe>=3.0.2
Markdown>=3.7 Markdown>=3.7
mdx-breakless-lists>=1.0.1 mdx-breakless-lists>=1.0.1

View File

@@ -213,7 +213,7 @@
{% endmacro %} {% endmacro %}
{% macro RandomizeButton(option_name, option) %} {% macro RandomizeButton(option_name, option) %}
<div class="randomize-button" data-tooltip="Toggle randomization for this option!"> <div class="randomize-button" data-tooltip="Pick a random value for this option.">
<label for="random-{{ option_name }}"> <label for="random-{{ option_name }}">
<input type="checkbox" id="random-{{ option_name }}" name="random-{{ option_name }}" class="randomize-checkbox" data-option-name="{{ option_name }}" {{ "checked" if option.default == "random" }} /> <input type="checkbox" id="random-{{ option_name }}" name="random-{{ option_name }}" class="randomize-checkbox" data-option-name="{{ option_name }}" {{ "checked" if option.default == "random" }} />
🎲 🎲

View File

@@ -100,7 +100,7 @@
{% else %} {% else %}
<div class="unsupported-option"> <div class="unsupported-option">
This option is not supported. Please edit your .yaml file manually. This option cannot be modified here. Please edit your .yaml file manually.
</div> </div>
{% endif %} {% endif %}

View File

@@ -386,7 +386,7 @@ if __name__ == '__main__':
parser.add_argument('diff_file', default="", type=str, nargs="?", parser.add_argument('diff_file', default="", type=str, nargs="?",
help='Path to a Archipelago Binary Patch file') help='Path to a Archipelago Binary Patch file')
args = parser.parse_args() args = parser.parse_args()
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main(args)) asyncio.run(main(args))
colorama.deinit() colorama.deinit()

View File

@@ -214,6 +214,9 @@
# Wargroove # Wargroove
/worlds/wargroove/ @FlySniper /worlds/wargroove/ @FlySniper
# The Wind Waker
/worlds/tww/ @tanjo3
# The Witness # The Witness
/worlds/witness/ @NewSoupVi @blastron /worlds/witness/ @NewSoupVi @blastron

View File

@@ -756,8 +756,8 @@ Tags are represented as a list of strings, the common client tags follow:
### DeathLink ### DeathLink
A special kind of Bounce packet that can be supported by any AP game. It targets the tag "DeathLink" and carries the following data: A special kind of Bounce packet that can be supported by any AP game. It targets the tag "DeathLink" and carries the following data:
| Name | Type | Notes | | Name | Type | Notes |
|--------|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |--------|-------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| time | float | Unix Time Stamp of time of death. | | time | float | Unix Time Stamp of time of death. |
| cause | str | Optional. Text to explain the cause of death. When provided, or checked, if the string is non-empty, it should contain the player name, ex. "Berserker was run over by a train." | | cause | str | Optional. Text to explain the cause of death. When provided, or checked, this should contain the player name, ex. "Berserker was run over by a train." |
| source | str | Name of the player who first died. Can be a slot name, but can also be a name from within a multiplayer game. | | source | str | Name of the player who first died. Can be a slot name, but can also be a name from within a multiplayer game. |

View File

@@ -82,6 +82,38 @@ Unit tests can also be created using [TestBase](/test/bases.py#L16) or
may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for may be useful for generating a multiworld under very specific constraints without using the generic world setup, or for
testing portions of your code that can be tested without relying on a multiworld to be created first. testing portions of your code that can be tested without relying on a multiworld to be created first.
#### Parametrization
When defining a test that needs to cover a range of inputs it is useful to parameterize (to run the same test
for multiple inputs) the base test. Some important things to consider when attempting to parametrize your test are:
* [Subtests](https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests)
can be used to have parametrized assertions that show up similar to individual tests but without the overhead
of needing to instantiate multiple tests; however, subtests can not be multithreaded and do not have individual
timing data, so they are not suitable for slow tests.
* Archipelago's tests are test-runner-agnostic. That means tests are not allowed to use e.g. `@pytest.mark.parametrize`.
Instead, we define our own parametrization helpers in [test.param](/test/param.py).
* Classes inheriting from `WorldTestBase`, including those created by the helpers in `test.param`, will run all
base tests by default, make sure the produced tests actually do what you aim for and do not waste a lot of
extra CPU time. Consider using `TestBase` or `unittest.TestCase` directly
or setting `WorldTestBase.run_default_tests` to False.
#### Performance Considerations
Archipelago is big enough that the runtime of unittests can have an impact on productivity.
Individual tests should take less than a second, so they can be properly multithreaded.
Ideally, thorough tests are directed at actual code/functionality. Do not just create and/or fill a ton of individual
Multiworlds that spend most of the test time outside what you actually want to test.
Consider generating/validating "random" games as part of your APWorld release workflow rather than having that be part
of continuous integration, and add minimal reproducers to the "normal" tests for problems that were found.
You can use [@unittest.skipIf](https://docs.python.org/3/library/unittest.html#unittest.skipIf) with an environment
variable to keep all the benefits of the test framework while not running the marked tests by default.
## Running Tests ## Running Tests
#### Using Pycharm #### Using Pycharm
@@ -100,3 +132,11 @@ next to the run and debug buttons.
#### Running Tests without Pycharm #### Running Tests without Pycharm
Run `pip install pytest pytest-subtests`, then use your IDE to run tests or run `pytest` from the source folder. Run `pip install pytest pytest-subtests`, then use your IDE to run tests or run `pytest` from the source folder.
#### Running Tests Multithreaded
pytest can run multiple test runners in parallel with the pytest-xdist extension.
Install with `pip install pytest-xdist`.
Run with `pytest -n12` to spawn 12 process that each run 1/12th of the tests.

View File

@@ -265,14 +265,19 @@ def bake_target_group_lookup(world: World, get_target_groups: Callable[[int], li
return { group: get_target_groups(group) for group in unique_groups } return { group: get_target_groups(group) for group in unique_groups }
def disconnect_entrance_for_randomization(entrance: Entrance, target_group: int | None = None) -> None: def disconnect_entrance_for_randomization(entrance: Entrance, target_group: int | None = None,
one_way_target_name: str | None = None) -> None:
""" """
Given an entrance in a "vanilla" region graph, splits that entrance to prepare it for randomization Given an entrance in a "vanilla" region graph, splits that entrance to prepare it for randomization
in randomize_entrances. This should be done after setting the type and group of the entrance. in randomize_entrances. This should be done after setting the type and group of the entrance. Because it attempts
to meet strict entrance naming requirements for coupled mode, this function may produce unintuitive results when
called only on a single entrance; it produces eventually-correct outputs only after calling it on all entrances.
:param entrance: The entrance which will be disconnected in preparation for randomization. :param entrance: The entrance which will be disconnected in preparation for randomization.
:param target_group: The group to assign to the created ER target. If not specified, the group from :param target_group: The group to assign to the created ER target. If not specified, the group from
the original entrance will be copied. the original entrance will be copied.
:param one_way_target_name: The name of the created ER target if `entrance` is one-way. This argument
is required for one-way entrances and is ignored otherwise.
""" """
child_region = entrance.connected_region child_region = entrance.connected_region
parent_region = entrance.parent_region parent_region = entrance.parent_region
@@ -287,8 +292,11 @@ def disconnect_entrance_for_randomization(entrance: Entrance, target_group: int
# targets in the child region will be created when the other direction edge is disconnected # targets in the child region will be created when the other direction edge is disconnected
target = parent_region.create_er_target(entrance.name) target = parent_region.create_er_target(entrance.name)
else: else:
# for 1-ways, the child region needs a target and coupling/naming is not a concern # for 1-ways, the child region needs a target. naming is not a concern for coupling so we
target = child_region.create_er_target(child_region.name) # allow it to be user provided (and require it, to prevent an unhelpful assumed name in pairings)
if not one_way_target_name:
raise ValueError("Cannot disconnect a one-way entrance without a target name specified")
target = child_region.create_er_target(one_way_target_name)
target.randomization_type = entrance.randomization_type target.randomization_type = entrance.randomization_type
target.randomization_group = target_group or entrance.randomization_group target.randomization_group = target_group or entrance.randomization_group
@@ -358,6 +366,34 @@ def randomize_entrances(
if on_connect: if on_connect:
on_connect(er_state, placed_exits) on_connect(er_state, placed_exits)
def needs_speculative_sweep(dead_end: bool, require_new_exits: bool, placeable_exits: list[Entrance]) -> bool:
# speculative sweep is expensive. We currently only do it as a last resort, if we might cap off the graph
# entirely
if len(placeable_exits) > 1:
return False
# in certain stages of randomization we either expect or don't care if the search space shrinks.
# we should never speculative sweep here.
if dead_end or not require_new_exits or not perform_validity_check:
return False
# edge case - if all dead ends have pre-placed progression or indirect connections, they are pulled forward
# into the non dead end stage. In this case, and only this case, it's possible that the last connection may
# actually be placeable in stage 1. We need to skip speculative sweep in this case because we expect the graph
# to get capped off.
# check to see if we are proposing the last placement
if not coupled:
# in uncoupled, this check is easy as there will only be one target.
is_last_placement = len(entrance_lookup) == 1
else:
# a bit harder, there may be 1 or 2 targets depending on if the exit to place is one way or two way.
# if it is two way, we can safely assume that one of the targets is the logical pair of the exit.
desired_target_count = 2 if placeable_exits[0].randomization_type == EntranceType.TWO_WAY else 1
is_last_placement = len(entrance_lookup) == desired_target_count
# if it's not the last placement, we need a sweep
return not is_last_placement
def find_pairing(dead_end: bool, require_new_exits: bool) -> bool: def find_pairing(dead_end: bool, require_new_exits: bool) -> bool:
nonlocal perform_validity_check nonlocal perform_validity_check
placeable_exits = er_state.find_placeable_exits(perform_validity_check, exits) placeable_exits = er_state.find_placeable_exits(perform_validity_check, exits)
@@ -371,11 +407,9 @@ def randomize_entrances(
# very last exit and check whatever exits we open up are functionally accessible. # very last exit and check whatever exits we open up are functionally accessible.
# this requirement can be ignored on a beaten minimal, islands are no issue there. # this requirement can be ignored on a beaten minimal, islands are no issue there.
exit_requirement_satisfied = (not perform_validity_check or not require_new_exits exit_requirement_satisfied = (not perform_validity_check or not require_new_exits
or target_entrance.connected_region not in er_state.placed_regions) or target_entrance.connected_region not in er_state.placed_regions)
needs_speculative_sweep = (not dead_end and require_new_exits and perform_validity_check
and len(placeable_exits) == 1)
if exit_requirement_satisfied and source_exit.can_connect_to(target_entrance, dead_end, er_state): if exit_requirement_satisfied and source_exit.can_connect_to(target_entrance, dead_end, er_state):
if (needs_speculative_sweep if (needs_speculative_sweep(dead_end, require_new_exits, placeable_exits)
and not er_state.test_speculative_connection(source_exit, target_entrance, exits_set)): and not er_state.test_speculative_connection(source_exit, target_entrance, exits_set)):
continue continue
do_placement(source_exit, target_entrance) do_placement(source_exit, target_entrance)

View File

@@ -817,6 +817,12 @@ class HintLayout(BoxLayout):
boxlayout.add_widget(AutocompleteHintInput()) boxlayout.add_widget(AutocompleteHintInput())
self.add_widget(boxlayout) self.add_widget(boxlayout)
def fix_heights(self):
for child in self.children:
fix_func = getattr(child, "fix_heights", None)
if fix_func:
fix_func()
status_names: typing.Dict[HintStatus, str] = { status_names: typing.Dict[HintStatus, str] = {
HintStatus.HINT_FOUND: "Found", HintStatus.HINT_FOUND: "Found",

View File

@@ -1,14 +1,14 @@
colorama>=0.4.6 colorama>=0.4.6
websockets>=13.0.1,<14 websockets>=13.0.1,<14
PyYAML>=6.0.2 PyYAML>=6.0.2
jellyfish>=1.1.0 jellyfish>=1.1.3
jinja2>=3.1.4 jinja2>=3.1.6
schema>=0.7.7 schema>=0.7.7
kivy>=2.3.0 kivy>=2.3.1
bsdiff4>=1.2.4 bsdiff4>=1.2.6
platformdirs>=4.2.2 platformdirs>=4.3.6
certifi>=2024.12.14 certifi>=2025.1.31
cython>=3.0.11 cython>=3.0.12
cymem>=2.0.8 cymem>=2.0.11
orjson>=3.10.7 orjson>=3.10.15
typing_extensions>=4.12.2 typing_extensions>=4.12.2

View File

@@ -19,7 +19,7 @@ from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it # This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
requirement = 'cx-Freeze==7.2.0' requirement = 'cx-Freeze==8.0.0'
try: try:
import pkg_resources import pkg_resources
try: try:

View File

@@ -148,7 +148,7 @@ class TestDisconnectForRandomization(unittest.TestCase):
e.randomization_group = 1 e.randomization_group = 1
e.connect(r2) e.connect(r2)
disconnect_entrance_for_randomization(e) disconnect_entrance_for_randomization(e, one_way_target_name="foo")
self.assertIsNone(e.connected_region) self.assertIsNone(e.connected_region)
self.assertEqual([], r1.entrances) self.assertEqual([], r1.entrances)
@@ -158,10 +158,22 @@ class TestDisconnectForRandomization(unittest.TestCase):
self.assertEqual(1, len(r2.entrances)) self.assertEqual(1, len(r2.entrances))
self.assertIsNone(r2.entrances[0].parent_region) self.assertIsNone(r2.entrances[0].parent_region)
self.assertEqual("r2", r2.entrances[0].name) self.assertEqual("foo", r2.entrances[0].name)
self.assertEqual(EntranceType.ONE_WAY, r2.entrances[0].randomization_type) self.assertEqual(EntranceType.ONE_WAY, r2.entrances[0].randomization_type)
self.assertEqual(1, r2.entrances[0].randomization_group) self.assertEqual(1, r2.entrances[0].randomization_group)
def test_disconnect_default_1way_no_vanilla_target_raises(self):
multiworld = generate_test_multiworld()
r1 = Region("r1", 1, multiworld)
r2 = Region("r2", 1, multiworld)
e = r1.create_exit("e")
e.randomization_type = EntranceType.ONE_WAY
e.randomization_group = 1
e.connect(r2)
with self.assertRaises(ValueError):
disconnect_entrance_for_randomization(e)
def test_disconnect_uses_alternate_group(self): def test_disconnect_uses_alternate_group(self):
multiworld = generate_test_multiworld() multiworld = generate_test_multiworld()
r1 = Region("r1", 1, multiworld) r1 = Region("r1", 1, multiworld)
@@ -171,7 +183,7 @@ class TestDisconnectForRandomization(unittest.TestCase):
e.randomization_group = 1 e.randomization_group = 1
e.connect(r2) e.connect(r2)
disconnect_entrance_for_randomization(e, 2) disconnect_entrance_for_randomization(e, 2, "foo")
self.assertIsNone(e.connected_region) self.assertIsNone(e.connected_region)
self.assertEqual([], r1.entrances) self.assertEqual([], r1.entrances)
@@ -181,7 +193,7 @@ class TestDisconnectForRandomization(unittest.TestCase):
self.assertEqual(1, len(r2.entrances)) self.assertEqual(1, len(r2.entrances))
self.assertIsNone(r2.entrances[0].parent_region) self.assertIsNone(r2.entrances[0].parent_region)
self.assertEqual("r2", r2.entrances[0].name) self.assertEqual("foo", r2.entrances[0].name)
self.assertEqual(EntranceType.ONE_WAY, r2.entrances[0].randomization_type) self.assertEqual(EntranceType.ONE_WAY, r2.entrances[0].randomization_type)
self.assertEqual(2, r2.entrances[0].randomization_group) self.assertEqual(2, r2.entrances[0].randomization_group)
@@ -218,7 +230,7 @@ class TestRandomizeEntrances(unittest.TestCase):
self.assertEqual(80, len(result.pairings)) self.assertEqual(80, len(result.pairings))
self.assertEqual(80, len(result.placements)) self.assertEqual(80, len(result.placements))
def test_coupling(self): def test_coupled(self):
"""tests that in coupled mode, all 2 way transitions have an inverse""" """tests that in coupled mode, all 2 way transitions have an inverse"""
multiworld = generate_test_multiworld() multiworld = generate_test_multiworld()
generate_disconnected_region_grid(multiworld, 5) generate_disconnected_region_grid(multiworld, 5)
@@ -236,6 +248,36 @@ class TestRandomizeEntrances(unittest.TestCase):
# if we didn't visit every placement the verification on_connect doesn't really mean much # if we didn't visit every placement the verification on_connect doesn't really mean much
self.assertEqual(len(result.placements), seen_placement_count) self.assertEqual(len(result.placements), seen_placement_count)
def test_uncoupled_succeeds_stage1_indirect_condition(self):
multiworld = generate_test_multiworld()
menu = multiworld.get_region("Menu", 1)
generate_entrance_pair(menu, "_right", ERTestGroups.RIGHT)
end = Region("End", 1, multiworld)
multiworld.regions.append(end)
generate_entrance_pair(end, "_left", ERTestGroups.LEFT)
multiworld.register_indirect_condition(end, None)
result = randomize_entrances(multiworld.worlds[1], False, directionally_matched_group_lookup)
self.assertSetEqual({
("Menu_right", "End_left"),
("End_left", "Menu_right")
}, set(result.pairings))
def test_coupled_succeeds_stage1_indirect_condition(self):
multiworld = generate_test_multiworld()
menu = multiworld.get_region("Menu", 1)
generate_entrance_pair(menu, "_right", ERTestGroups.RIGHT)
end = Region("End", 1, multiworld)
multiworld.regions.append(end)
generate_entrance_pair(end, "_left", ERTestGroups.LEFT)
multiworld.register_indirect_condition(end, None)
result = randomize_entrances(multiworld.worlds[1], True, directionally_matched_group_lookup)
self.assertSetEqual({
("Menu_right", "End_left"),
("End_left", "Menu_right")
}, set(result.pairings))
def test_uncoupled(self): def test_uncoupled(self):
"""tests that in uncoupled mode, no transitions have an (intentional) inverse""" """tests that in uncoupled mode, no transitions have an (intentional) inverse"""
multiworld = generate_test_multiworld() multiworld = generate_test_multiworld()

View File

@@ -0,0 +1,14 @@
import unittest
import os
class TestPackages(unittest.TestCase):
def test_packages_have_init(self):
"""Test that all world folders containing .py files also have a __init__.py file,
to indicate full package rather than namespace package."""
import Utils
worlds_path = Utils.local_path("worlds")
for dirpath, dirnames, filenames in os.walk(worlds_path):
with self.subTest(directory=dirpath):
self.assertEqual("__init__.py" in filenames, any(file.endswith(".py") for file in filenames))

View File

@@ -0,0 +1,11 @@
import unittest
from worlds.AutoWorld import AutoWorldRegister
from worlds.Files import AutoPatchRegister
class TestPatches(unittest.TestCase):
def test_patch_name_matches_game(self) -> None:
for game_name in AutoPatchRegister.patch_types:
with self.subTest(game=game_name):
self.assertIn(game_name, AutoWorldRegister.world_types.keys(),
f"Patch '{game_name}' does not match the name of any world.")

View File

@@ -0,0 +1,19 @@
import unittest
import os
class TestBase(unittest.TestCase):
def test_requirements_file_ends_on_newline(self):
"""Test that all requirements files end on a newline"""
import Utils
requirements_files = [Utils.local_path("requirements.txt"),
Utils.local_path("WebHostLib", "requirements.txt")]
worlds_path = Utils.local_path("worlds")
for entry in os.listdir(worlds_path):
requirements_path = os.path.join(worlds_path, entry, "requirements.txt")
if os.path.isfile(requirements_path):
requirements_files.append(requirements_path)
for requirements_file in requirements_files:
with self.subTest(path=requirements_file):
with open(requirements_file) as f:
self.assertEqual(f.read()[-1], "\n")

View File

@@ -1,5 +1,5 @@
import unittest import unittest
from typing import List, Tuple from typing import ClassVar, List, Tuple
from unittest import TestCase from unittest import TestCase
from BaseClasses import CollectionState, Location, MultiWorld from BaseClasses import CollectionState, Location, MultiWorld
@@ -7,6 +7,7 @@ from Fill import distribute_items_restrictive
from Options import Accessibility from Options import Accessibility
from worlds.AutoWorld import AutoWorldRegister, call_all, call_single from worlds.AutoWorld import AutoWorldRegister, call_all, call_single
from ..general import gen_steps, setup_multiworld from ..general import gen_steps, setup_multiworld
from ..param import classvar_matrix
class MultiworldTestBase(TestCase): class MultiworldTestBase(TestCase):
@@ -63,15 +64,18 @@ class TestAllGamesMultiworld(MultiworldTestBase):
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game") self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
@classvar_matrix(game=AutoWorldRegister.world_types.keys())
class TestTwoPlayerMulti(MultiworldTestBase): class TestTwoPlayerMulti(MultiworldTestBase):
game: ClassVar[str]
def test_two_player_single_game_fills(self) -> None: def test_two_player_single_game_fills(self) -> None:
"""Tests that a multiworld of two players for each registered game world can generate.""" """Tests that a multiworld of two players for each registered game world can generate."""
for world_type in AutoWorldRegister.world_types.values(): world_type = AutoWorldRegister.world_types[self.game]
self.multiworld = setup_multiworld([world_type, world_type], ()) self.multiworld = setup_multiworld([world_type, world_type], ())
for world in self.multiworld.worlds.values(): for world in self.multiworld.worlds.values():
world.options.accessibility.value = Accessibility.option_full world.options.accessibility.value = Accessibility.option_full
self.assertSteps(gen_steps) self.assertSteps(gen_steps)
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed): with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
distribute_items_restrictive(self.multiworld) distribute_items_restrictive(self.multiworld)
call_all(self.multiworld, "post_fill") call_all(self.multiworld, "post_fill")
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game") self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")

46
test/param.py Normal file
View File

@@ -0,0 +1,46 @@
import itertools
import sys
from typing import Any, Callable, Iterable
def classvar_matrix(**kwargs: Iterable[Any]) -> Callable[[type], None]:
"""
Create a new class for each variation of input, allowing to generate a TestCase matrix / parametrization that
supports multi-threading and has better reporting for ``unittest --durations=...`` and ``pytest --durations=...``
than subtests.
The kwargs will be set as ClassVars in the newly created classes. Use as ::
@classvar_matrix(var_name=[value1, value2])
class MyTestCase(unittest.TestCase):
var_name: typing.ClassVar[...]
:param kwargs: A dict of ClassVars to set, where key is the variable name and value is a list of all values.
:return: A decorator to be applied to a class.
"""
keys: tuple[str]
values: Iterable[Iterable[Any]]
keys, values = zip(*kwargs.items())
values = map(lambda v: sorted(v) if isinstance(v, (set, frozenset)) else v, values)
permutations_dicts = [dict(zip(keys, v)) for v in itertools.product(*values)]
def decorator(cls: type) -> None:
mod = sys.modules[cls.__module__]
for permutation in permutations_dicts:
class Unrolled(cls): # type: ignore
pass
for k, v in permutation.items():
setattr(Unrolled, k, v)
params = ", ".join([f"{k}={repr(v)}" for k, v in permutation.items()])
params = f"{{{params}}}"
Unrolled.__module__ = cls.__module__
Unrolled.__qualname__ = f"{cls.__qualname__}{params}"
setattr(mod, f"{cls.__name__}{params}", Unrolled)
return None
return decorator

View File

@@ -276,6 +276,6 @@ def launch(*launch_args: str) -> None:
Utils.init_logging("BizHawkClient", exception_logger="Client") Utils.init_logging("BizHawkClient", exception_logger="Client")
import colorama import colorama
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -3,4 +3,4 @@ mpyq>=0.2.5
portpicker>=1.5.2 portpicker>=1.5.2
aiohttp>=3.8.4 aiohttp>=3.8.4
loguru>=0.7.0 loguru>=0.7.0
protobuf==3.20.3 protobuf==3.20.3

View File

@@ -261,6 +261,6 @@ def launch():
# options = Utils.get_options() # options = Utils.get_options()
import colorama import colorama
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main()) asyncio.run(main())
colorama.deinit() colorama.deinit()

View File

@@ -515,10 +515,15 @@ def _populate_sprite_table():
logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.") logging.debug(f"Spritefile {file} could not be loaded as a valid sprite.")
with concurrent.futures.ThreadPoolExecutor() as pool: with concurrent.futures.ThreadPoolExecutor() as pool:
for dir in [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')]: sprite_paths = [user_path('data', 'sprites', 'alttpr'), user_path('data', 'sprites', 'custom')]
for dir in [dir for dir in sprite_paths if os.path.isdir(dir)]:
for file in os.listdir(dir): for file in os.listdir(dir):
pool.submit(load_sprite_from_file, os.path.join(dir, file)) pool.submit(load_sprite_from_file, os.path.join(dir, file))
if "link" not in _sprite_table:
logging.info("Link sprite was not loaded. Loading link from base rom")
load_sprite_from_file(get_base_rom_path())
class Sprite(): class Sprite():
sprite_size = 28672 sprite_size = 28672
@@ -554,6 +559,11 @@ class Sprite():
self.sprite = filedata[0x80000:0x87000] self.sprite = filedata[0x80000:0x87000]
self.palette = filedata[0xDD308:0xDD380] self.palette = filedata[0xDD308:0xDD380]
self.glove_palette = filedata[0xDEDF5:0xDEDF9] self.glove_palette = filedata[0xDEDF5:0xDEDF9]
h = hashlib.md5()
h.update(filedata)
if h.hexdigest() == LTTPJPN10HASH:
self.name = "Link"
self.author_name = "Nintendo"
elif filedata.startswith(b'ZSPR'): elif filedata.startswith(b'ZSPR'):
self.from_zspr(filedata, filename) self.from_zspr(filedata, filename)
else: else:

View File

@@ -1,2 +1,2 @@
maseya-z3pr>=1.0.0rc1 maseya-z3pr>=1.0.0rc1
xxtea>=3.0.0 xxtea>=3.0.0

View File

@@ -1,6 +1,31 @@
# Celeste 64 - Changelog # Celeste 64 - Changelog
## v1.3
### Features:
- New optional Location Checks
- Checkpointsanity
- Hair Color
- Allows for setting of Maddy's hair color in each of No Dash, One Dash, Two Dash, and Feather states
- Other Player Ghosts
- A game config option allows you to see ghosts of other Celeste 64 players in the multiworld
### Quality of Life:
- Checkpoint Warping
- Received Checkpoint items allow for warping to their respective checkpoint
- These items are on their respective checkpoint location if Checkpointsanity is disabled
- Logic accounts for being able to warp to otherwise inaccessible areas
- Checkpoints are a possible option for a starting item on Standard Logic + Move Shuffle + Checkpointsanity
- New Options toggle to enable/disable background input
### Bug Fixes:
- Traffic Blocks now correctly appear disabled within Cassettes
## v1.2 ## v1.2
### Features: ### Features:

View File

@@ -39,6 +39,22 @@ move_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.climb: Celeste64ItemData(celeste_64_base_id + 0xD, 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} checkpoint_item_data_table: Dict[str, Celeste64ItemData] = {
ItemName.checkpoint_1: Celeste64ItemData(celeste_64_base_id + 0x20, ItemClassification.progression),
ItemName.checkpoint_2: Celeste64ItemData(celeste_64_base_id + 0x21, ItemClassification.progression),
ItemName.checkpoint_3: Celeste64ItemData(celeste_64_base_id + 0x22, ItemClassification.progression),
ItemName.checkpoint_4: Celeste64ItemData(celeste_64_base_id + 0x23, ItemClassification.progression),
ItemName.checkpoint_5: Celeste64ItemData(celeste_64_base_id + 0x24, ItemClassification.progression),
ItemName.checkpoint_6: Celeste64ItemData(celeste_64_base_id + 0x25, ItemClassification.progression),
ItemName.checkpoint_7: Celeste64ItemData(celeste_64_base_id + 0x26, ItemClassification.progression),
ItemName.checkpoint_8: Celeste64ItemData(celeste_64_base_id + 0x27, ItemClassification.progression),
ItemName.checkpoint_9: Celeste64ItemData(celeste_64_base_id + 0x28, ItemClassification.progression),
ItemName.checkpoint_10: Celeste64ItemData(celeste_64_base_id + 0x29, ItemClassification.progression),
}
item_data_table: Dict[str, Celeste64ItemData] = {**collectable_item_data_table,
**unlockable_item_data_table,
**move_item_data_table,
**checkpoint_item_data_table}
item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None} item_table = {name: data.code for name, data in item_data_table.items() if data.code is not None}

View File

@@ -1,7 +1,7 @@
from typing import Dict, NamedTuple, Optional from typing import Dict, NamedTuple, Optional
from BaseClasses import Location from BaseClasses import Location
from .Names import LocationName from .Names import LocationName, RegionName
celeste_64_base_id: int = 0xCA0000 celeste_64_base_id: int = 0xCA0000
@@ -17,66 +17,80 @@ class Celeste64LocationData(NamedTuple):
strawberry_location_data_table: Dict[str, Celeste64LocationData] = { strawberry_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.strawberry_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x00), LocationName.strawberry_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x00),
LocationName.strawberry_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x01), LocationName.strawberry_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x01),
LocationName.strawberry_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x02), LocationName.strawberry_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x02),
LocationName.strawberry_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x03), LocationName.strawberry_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x03),
LocationName.strawberry_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x04), LocationName.strawberry_5: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x04),
LocationName.strawberry_6: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x05), LocationName.strawberry_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x05),
LocationName.strawberry_7: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x06), LocationName.strawberry_7: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x06),
LocationName.strawberry_8: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x07), LocationName.strawberry_8: Celeste64LocationData(RegionName.nw_girders_island, celeste_64_base_id + 0x07),
LocationName.strawberry_9: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x08), LocationName.strawberry_9: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x08),
LocationName.strawberry_10: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x09), LocationName.strawberry_10: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x09),
LocationName.strawberry_11: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0A), LocationName.strawberry_11: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x0A),
LocationName.strawberry_12: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0B), LocationName.strawberry_12: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x0B),
LocationName.strawberry_13: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0C), LocationName.strawberry_13: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x0C),
LocationName.strawberry_14: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0D), LocationName.strawberry_14: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0D),
LocationName.strawberry_15: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0E), LocationName.strawberry_15: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0E),
LocationName.strawberry_16: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x0F), LocationName.strawberry_16: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x0F),
LocationName.strawberry_17: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x10), LocationName.strawberry_17: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x10),
LocationName.strawberry_18: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x11), LocationName.strawberry_18: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x11),
LocationName.strawberry_19: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x12), LocationName.strawberry_19: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x12),
LocationName.strawberry_20: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x13), LocationName.strawberry_20: Celeste64LocationData(RegionName.badeline_tower_lower, celeste_64_base_id + 0x13),
LocationName.strawberry_21: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x14), LocationName.strawberry_21: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x14),
LocationName.strawberry_22: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x15), LocationName.strawberry_22: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x15),
LocationName.strawberry_23: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x16), LocationName.strawberry_23: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x16),
LocationName.strawberry_24: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x17), LocationName.strawberry_24: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x17),
LocationName.strawberry_25: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x18), LocationName.strawberry_25: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x18),
LocationName.strawberry_26: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x19), LocationName.strawberry_26: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x19),
LocationName.strawberry_27: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1A), LocationName.strawberry_27: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1A),
LocationName.strawberry_28: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1B), LocationName.strawberry_28: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x1B),
LocationName.strawberry_29: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1C), LocationName.strawberry_29: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x1C),
LocationName.strawberry_30: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x1D), LocationName.strawberry_30: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x1D),
} }
friend_location_data_table: Dict[str, Celeste64LocationData] = { friend_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.granny_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x00), LocationName.granny_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x00),
LocationName.granny_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x01), LocationName.granny_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x01),
LocationName.granny_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x02), LocationName.granny_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x02),
LocationName.theo_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x03), LocationName.theo_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x03),
LocationName.theo_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x04), LocationName.theo_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x04),
LocationName.theo_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x05), LocationName.theo_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x100 + 0x05),
LocationName.badeline_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x06), LocationName.badeline_1: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x06),
LocationName.badeline_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x07), LocationName.badeline_2: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x07),
LocationName.badeline_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x100 + 0x08), LocationName.badeline_3: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x100 + 0x08),
} }
sign_location_data_table: Dict[str, Celeste64LocationData] = { sign_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.sign_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x00), LocationName.sign_1: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x00),
LocationName.sign_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x01), LocationName.sign_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x200 + 0x01),
LocationName.sign_3: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x02), LocationName.sign_3: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x200 + 0x02),
LocationName.sign_4: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x03), LocationName.sign_4: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x200 + 0x03),
LocationName.sign_5: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x200 + 0x04), LocationName.sign_5: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x200 + 0x04),
} }
car_location_data_table: Dict[str, Celeste64LocationData] = { car_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.car_1: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x00), LocationName.car_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x300 + 0x00),
LocationName.car_2: Celeste64LocationData("Forsaken City", celeste_64_base_id + 0x300 + 0x01), LocationName.car_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x300 + 0x01),
}
checkpoint_location_data_table: Dict[str, Celeste64LocationData] = {
LocationName.checkpoint_1: Celeste64LocationData(RegionName.intro_islands, celeste_64_base_id + 0x400 + 0x00),
LocationName.checkpoint_2: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x01),
LocationName.checkpoint_3: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x02),
LocationName.checkpoint_4: Celeste64LocationData(RegionName.granny_island, celeste_64_base_id + 0x400 + 0x03),
LocationName.checkpoint_5: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x04),
LocationName.checkpoint_6: Celeste64LocationData(RegionName.highway_island, celeste_64_base_id + 0x400 + 0x05),
LocationName.checkpoint_7: Celeste64LocationData(RegionName.ne_feathers_island, celeste_64_base_id + 0x400 + 0x06),
LocationName.checkpoint_8: Celeste64LocationData(RegionName.se_house_island, celeste_64_base_id + 0x400 + 0x07),
LocationName.checkpoint_9: Celeste64LocationData(RegionName.badeline_tower_upper, celeste_64_base_id + 0x400 + 0x08),
LocationName.checkpoint_10: Celeste64LocationData(RegionName.badeline_island, celeste_64_base_id + 0x400 + 0x09),
} }
location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table, location_data_table: Dict[str, Celeste64LocationData] = {**strawberry_location_data_table,
**friend_location_data_table, **friend_location_data_table,
**sign_location_data_table, **sign_location_data_table,
**car_location_data_table} **car_location_data_table,
**checkpoint_location_data_table}
location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None} location_table = {name: data.address for name, data in location_data_table.items() if data.address is not None}

View File

@@ -15,3 +15,18 @@ ground_dash = "Ground Dash"
air_dash = "Air Dash" air_dash = "Air Dash"
skid_jump = "Skid Jump" skid_jump = "Skid Jump"
climb = "Climb" climb = "Climb"
# Checkpoint Items
checkpoint_1 = "Intro Checkpoint"
checkpoint_2 = "Granny Checkpoint"
checkpoint_3 = "South-East Tower Checkpoint"
checkpoint_4 = "Climb Sign Checkpoint"
checkpoint_5 = "Freeway Checkpoint"
checkpoint_6 = "Freeway Feather Checkpoint"
checkpoint_7 = "Feather Maze Checkpoint"
checkpoint_8 = "Double Dash House Checkpoint"
checkpoint_9 = "Badeline Tower Checkpoint"
checkpoint_10 = "Badeline Island Checkpoint"
# Item used for logic definitions that are not possible with the given options
cannot_access = "CANNOT ACCESS"

View File

@@ -10,7 +10,7 @@ strawberry_8 = "Traffic Block Strawberry"
strawberry_9 = "South-West Dash Refills Strawberry" strawberry_9 = "South-West Dash Refills Strawberry"
strawberry_10 = "South-East Tower Side Strawberry" strawberry_10 = "South-East Tower Side Strawberry"
strawberry_11 = "Girders Strawberry" strawberry_11 = "Girders Strawberry"
strawberry_12 = "North-East Tower Bottom Strawberry" strawberry_12 = "Badeline Tower Bottom Strawberry"
strawberry_13 = "Breakable Blocks Strawberry" strawberry_13 = "Breakable Blocks Strawberry"
strawberry_14 = "Feather Maze Strawberry" strawberry_14 = "Feather Maze Strawberry"
strawberry_15 = "Feather Chain Strawberry" strawberry_15 = "Feather Chain Strawberry"
@@ -18,7 +18,7 @@ strawberry_16 = "Feather Hidden Strawberry"
strawberry_17 = "Double Dash Puzzle Strawberry" strawberry_17 = "Double Dash Puzzle Strawberry"
strawberry_18 = "Double Dash Spike Climb Strawberry" strawberry_18 = "Double Dash Spike Climb Strawberry"
strawberry_19 = "Double Dash Spring Strawberry" strawberry_19 = "Double Dash Spring Strawberry"
strawberry_20 = "North-East Tower Breakable Bottom Strawberry" strawberry_20 = "Badeline Tower Breakable Bottom Strawberry"
strawberry_21 = "Theo Tower Lower Cassette Strawberry" strawberry_21 = "Theo Tower Lower Cassette Strawberry"
strawberry_22 = "Theo Tower Upper Cassette Strawberry" strawberry_22 = "Theo Tower Upper Cassette Strawberry"
strawberry_23 = "South End of Bridge Cassette Strawberry" strawberry_23 = "South End of Bridge Cassette Strawberry"
@@ -27,8 +27,8 @@ strawberry_25 = "Cassette Hidden in the House Strawberry"
strawberry_26 = "North End of Bridge Cassette Strawberry" strawberry_26 = "North End of Bridge Cassette Strawberry"
strawberry_27 = "Distant Feather Cassette Strawberry" strawberry_27 = "Distant Feather Cassette Strawberry"
strawberry_28 = "Feather Arches Cassette Strawberry" strawberry_28 = "Feather Arches Cassette Strawberry"
strawberry_29 = "North-East Tower Cassette Strawberry" strawberry_29 = "Badeline Tower Cassette Strawberry"
strawberry_30 = "Badeline Cassette Strawberry" strawberry_30 = "Badeline Island Cassette Strawberry"
# Friend Locations # Friend Locations
granny_1 = "Granny Conversation 1" granny_1 = "Granny Conversation 1"
@@ -51,3 +51,15 @@ sign_5 = "Credits Sign"
# Car Locations # Car Locations
car_1 = "Intro Car" car_1 = "Intro Car"
car_2 = "Secret Car" car_2 = "Secret Car"
# Checkpoint Locations
checkpoint_1 = "Intro Checkpoint"
checkpoint_2 = "Granny Checkpoint"
checkpoint_3 = "South-East Tower Checkpoint"
checkpoint_4 = "Climb Sign Checkpoint"
checkpoint_5 = "Freeway Checkpoint"
checkpoint_6 = "Freeway Feather Checkpoint"
checkpoint_7 = "Feather Maze Checkpoint"
checkpoint_8 = "Double Dash House Checkpoint"
checkpoint_9 = "Badeline Tower Checkpoint"
checkpoint_10 = "Badeline Island Checkpoint"

View File

@@ -0,0 +1,13 @@
# Level Base Regions
forsaken_city = "Forsaken City"
# Forsaken City Regions
intro_islands = "Intro Islands"
granny_island = "Granny Island"
highway_island = "Freeway Island"
nw_girders_island = "North-West Girders Island"
ne_feathers_island = "North-East Feathers Island"
se_house_island = "South-East House Island"
badeline_tower_lower = "Badeline Tower Lower"
badeline_tower_upper = "Badeline Tower Upper"
badeline_island = "Badeline Island"

View File

@@ -1,6 +1,8 @@
from dataclasses import dataclass from dataclasses import dataclass
import random
from Options import Choice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions from Options import Choice, TextChoice, Range, Toggle, DeathLink, OptionGroup, PerGameCommonOptions, OptionError
from worlds.AutoWorld import World
class DeathLinkAmnesty(Range): class DeathLinkAmnesty(Range):
@@ -18,7 +20,7 @@ class TotalStrawberries(Range):
""" """
display_name = "Total Strawberries" display_name = "Total Strawberries"
range_start = 0 range_start = 0
range_end = 46 range_end = 55
default = 20 default = 20
class StrawberriesRequiredPercentage(Range): class StrawberriesRequiredPercentage(Range):
@@ -73,6 +75,93 @@ class Carsanity(Toggle):
""" """
display_name = "Carsanity" display_name = "Carsanity"
class Checkpointsanity(Toggle):
"""
Whether activating Checkpoints grants location checks
Activating this will also shuffle items into the pool which allow usage and warping to each Checkpoint
"""
display_name = "Checkpointsanity"
class ColorChoice(TextChoice):
option_strawberry = 0xDB2C00
option_empty = 0x6EC0FF
option_double = 0xFA91FF
option_golden = 0xF2D450
option_baddy = 0x9B3FB5
option_fire_red = 0xFF0000
option_maroon = 0x800000
option_salmon = 0xFF3A65
option_orange = 0xD86E0A
option_lime_green = 0x8DF920
option_bright_green = 0x0DAF05
option_forest_green = 0x132818
option_royal_blue = 0x0036BF
option_brown = 0xB78726
option_black = 0x000000
option_white = 0xFFFFFF
option_grey = 0x808080
option_any_color = -1
@classmethod
def from_text(cls, text: str) -> Choice:
text = text.lower()
if text == "random":
choice_list = list(cls.name_lookup)
choice_list.remove(cls.option_any_color)
return cls(random.choice(choice_list))
return super().from_text(text)
class MadelineOneDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has one dash
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline One Dash Hair Color"
default = ColorChoice.option_strawberry
class MadelineTwoDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has two dashes
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline Two Dash Hair Color"
default = ColorChoice.option_double
class MadelineNoDashHairColor(ColorChoice):
"""
What color Madeline's hair is when she has no dashes
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline No Dash Hair Color"
default = ColorChoice.option_empty
class MadelineFeatherHairColor(ColorChoice):
"""
What color Madeline's hair is when she has a feather
The `any_color` option will choose a fully random color
A custom color entry may be supplied as a 6-character RGB hex color code
e.g. F542C8
"""
display_name = "Madeline Feather Hair Color"
default = ColorChoice.option_golden
class BadelineChaserSource(Choice): class BadelineChaserSource(Choice):
""" """
@@ -119,6 +208,13 @@ celeste_64_option_groups = [
Friendsanity, Friendsanity,
Signsanity, Signsanity,
Carsanity, Carsanity,
Checkpointsanity,
]),
OptionGroup("Aesthetic Options", [
MadelineOneDashHairColor,
MadelineTwoDashHairColor,
MadelineNoDashHairColor,
MadelineFeatherHairColor,
]), ]),
OptionGroup("Badeline Chasers", [ OptionGroup("Badeline Chasers", [
BadelineChaserSource, BadelineChaserSource,
@@ -142,7 +238,68 @@ class Celeste64Options(PerGameCommonOptions):
friendsanity: Friendsanity friendsanity: Friendsanity
signsanity: Signsanity signsanity: Signsanity
carsanity: Carsanity carsanity: Carsanity
checkpointsanity: Checkpointsanity
madeline_one_dash_hair_color: MadelineOneDashHairColor
madeline_two_dash_hair_color: MadelineTwoDashHairColor
madeline_no_dash_hair_color: MadelineNoDashHairColor
madeline_feather_hair_color: MadelineFeatherHairColor
badeline_chaser_source: BadelineChaserSource badeline_chaser_source: BadelineChaserSource
badeline_chaser_frequency: BadelineChaserFrequency badeline_chaser_frequency: BadelineChaserFrequency
badeline_chaser_speed: BadelineChaserSpeed badeline_chaser_speed: BadelineChaserSpeed
def resolve_options(world: World):
# One Dash Hair
if isinstance(world.options.madeline_one_dash_hair_color.value, str):
try:
world.madeline_one_dash_hair_color = int(world.options.madeline_one_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_one_dash_hair_color`:"
f"{world.options.madeline_one_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_one_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_one_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_one_dash_hair_color = world.options.madeline_one_dash_hair_color.value
# Two Dash Hair
if isinstance(world.options.madeline_two_dash_hair_color.value, str):
try:
world.madeline_two_dash_hair_color = int(world.options.madeline_two_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_two_dash_hair_color`:"
f"{world.options.madeline_two_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_two_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_two_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_two_dash_hair_color = world.options.madeline_two_dash_hair_color.value
# No Dash Hair
if isinstance(world.options.madeline_no_dash_hair_color.value, str):
try:
world.madeline_no_dash_hair_color = int(world.options.madeline_no_dash_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_no_dash_hair_color`:"
f"{world.options.madeline_no_dash_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_no_dash_hair_color.value == ColorChoice.option_any_color:
world.madeline_no_dash_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_no_dash_hair_color = world.options.madeline_no_dash_hair_color.value
# Feather Hair
if isinstance(world.options.madeline_feather_hair_color.value, str):
try:
world.madeline_feather_hair_color = int(world.options.madeline_feather_hair_color.value.strip("#")[:6], 16)
except ValueError:
raise OptionError(f"Invalid input for option `madeline_feather_hair_color`:"
f"{world.options.madeline_feather_hair_color.value} for "
f"{world.player_name}")
elif world.options.madeline_feather_hair_color.value == ColorChoice.option_any_color:
world.madeline_feather_hair_color = world.random.randint(0, 0xFFFFFF)
else:
world.madeline_feather_hair_color = world.options.madeline_feather_hair_color.value

View File

@@ -1,11 +1,23 @@
from typing import Dict, List, NamedTuple from typing import Dict, List, NamedTuple
from .Names import RegionName
class Celeste64RegionData(NamedTuple): class Celeste64RegionData(NamedTuple):
connecting_regions: List[str] = [] connecting_regions: List[str] = []
region_data_table: Dict[str, Celeste64RegionData] = { region_data_table: Dict[str, Celeste64RegionData] = {
"Menu": Celeste64RegionData(["Forsaken City"]), "Menu": Celeste64RegionData([RegionName.forsaken_city]),
"Forsaken City": Celeste64RegionData(),
RegionName.forsaken_city: Celeste64RegionData([RegionName.intro_islands, RegionName.granny_island, RegionName.highway_island, RegionName.ne_feathers_island, RegionName.se_house_island, RegionName.badeline_tower_upper, RegionName.badeline_island]),
RegionName.intro_islands: Celeste64RegionData([RegionName.granny_island]),
RegionName.granny_island: Celeste64RegionData([RegionName.highway_island, RegionName.nw_girders_island, RegionName.badeline_tower_lower, RegionName.se_house_island]),
RegionName.highway_island: Celeste64RegionData([RegionName.granny_island, RegionName.ne_feathers_island, RegionName.nw_girders_island]),
RegionName.nw_girders_island: Celeste64RegionData([RegionName.highway_island]),
RegionName.ne_feathers_island: Celeste64RegionData([RegionName.se_house_island, RegionName.highway_island, RegionName.badeline_tower_lower, RegionName.badeline_tower_upper]),
RegionName.se_house_island: Celeste64RegionData([RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_lower]),
RegionName.badeline_tower_lower: Celeste64RegionData([RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island, RegionName.badeline_tower_upper]),
RegionName.badeline_tower_upper: Celeste64RegionData([RegionName.badeline_island, RegionName.badeline_tower_lower, RegionName.se_house_island, RegionName.ne_feathers_island, RegionName.granny_island]),
RegionName.badeline_island: Celeste64RegionData([RegionName.badeline_tower_upper, RegionName.granny_island, RegionName.highway_island]),
} }

View File

@@ -1,265 +1,85 @@
from typing import Dict, List from typing import Dict, List, Tuple, Callable
from BaseClasses import CollectionState from BaseClasses import CollectionState, Region
from worlds.generic.Rules import set_rule from worlds.generic.Rules import set_rule
from . import Celeste64World from . import Celeste64World
from .Names import ItemName, LocationName from .Names import ItemName, LocationName, RegionName
def set_rules(world: Celeste64World): def set_rules(world: Celeste64World):
if world.options.logic_difficulty == "standard": if world.options.logic_difficulty == "standard":
if world.options.move_shuffle: world.active_logic_mapping = location_standard_moves_logic
world.active_logic_mapping = location_standard_moves_logic world.active_region_logic_mapping = region_standard_moves_logic
else:
world.active_logic_mapping = location_standard_logic
else: else:
if world.options.move_shuffle: world.active_logic_mapping = location_hard_moves_logic
world.active_logic_mapping = location_hard_moves_logic world.active_region_logic_mapping = region_hard_moves_logic
else:
world.active_logic_mapping = location_hard_logic
for location in world.multiworld.get_locations(world.player): for location in world.multiworld.get_locations(world.player):
set_rule(location, lambda state, location=location: location_rule(state, world, location.name)) 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. # Completion condition.
world.multiworld.completion_condition[world.player] = lambda state: goal_rule(state, world) 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]]] = { location_standard_moves_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_1: [[ItemName.ground_dash], LocationName.strawberry_1: [[ItemName.ground_dash],
[ItemName.air_dash], [ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
LocationName.strawberry_2: [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]], [ItemName.climb]],
LocationName.strawberry_2: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_3: [[ItemName.air_dash], LocationName.strawberry_3: [[ItemName.air_dash],
[ItemName.skid_jump]], [ItemName.skid_jump]],
LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], LocationName.strawberry_4: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_5: [[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_9: [[ItemName.dash_refill, ItemName.air_dash]],
LocationName.strawberry_10: [[ItemName.climb]], LocationName.strawberry_10: [[ItemName.climb]],
LocationName.strawberry_11: [[ItemName.dash_refill, ItemName.air_dash, ItemName.climb], LocationName.strawberry_11: [[ItemName.air_dash, ItemName.climb]],
[ItemName.traffic_block, ItemName.climb]], LocationName.strawberry_13: [[ItemName.breakables, ItemName.air_dash],
LocationName.strawberry_12: [[ItemName.dash_refill, ItemName.double_dash_refill, ItemName.air_dash], [ItemName.breakables, ItemName.ground_dash]],
[ItemName.traffic_block, ItemName.double_dash_refill, ItemName.air_dash]], LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash]],
LocationName.strawberry_13: [[ItemName.dash_refill, ItemName.breakables, ItemName.air_dash], LocationName.strawberry_15: [[ItemName.feather, ItemName.air_dash, ItemName.climb]],
[ItemName.traffic_block, ItemName.breakables, ItemName.ground_dash], LocationName.strawberry_16: [[ItemName.feather]],
[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]], LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block]],
LocationName.strawberry_14: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash], LocationName.strawberry_18: [[ItemName.double_dash_refill, ItemName.air_dash, ItemName.climb]],
[ItemName.traffic_block, ItemName.feather, ItemName.air_dash]], LocationName.strawberry_19: [[ItemName.double_dash_refill, ItemName.spring, ItemName.air_dash, ItemName.skid_jump]],
LocationName.strawberry_15: [[ItemName.dash_refill, ItemName.feather, ItemName.air_dash, ItemName.climb], LocationName.strawberry_20: [[ItemName.feather, ItemName.breakables, ItemName.air_dash]],
[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_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_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], LocationName.strawberry_23: [[ItemName.cassette, 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_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], LocationName.strawberry_25: [[ItemName.cassette, 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.air_dash, ItemName.climb]],
LocationName.strawberry_26: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.climb], LocationName.strawberry_27: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
[ItemName.cassette, ItemName.traffic_block, ItemName.air_dash, ItemName.climb]], LocationName.strawberry_28: [[ItemName.cassette, ItemName.feather, ItemName.coin, ItemName.air_dash, ItemName.climb]],
LocationName.strawberry_27: [[ItemName.cassette, ItemName.dash_refill, ItemName.feather, ItemName.coin, ItemName.air_dash], LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.coin, ItemName.air_dash, ItemName.skid_jump]],
[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.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_1: [[ItemName.traffic_block, ItemName.breakables, ItemName.air_dash]],
LocationName.theo_2: [[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.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], LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_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], LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash, ItemName.climb],
[ItemName.breakables, ItemName.air_dash]], [ItemName.breakables, ItemName.air_dash, ItemName.climb]],
} }
location_hard_moves_logic: Dict[str, List[List[str]]] = { location_hard_moves_logic: Dict[str, List[List[str]]] = {
LocationName.strawberry_3: [[ItemName.air_dash],
[ItemName.skid_jump]],
LocationName.strawberry_5: [[ItemName.ground_dash], LocationName.strawberry_5: [[ItemName.ground_dash],
[ItemName.air_dash]], [ItemName.air_dash]],
LocationName.strawberry_8: [[ItemName.traffic_block],
[ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_10: [[ItemName.air_dash], LocationName.strawberry_10: [[ItemName.air_dash],
[ItemName.climb]], [ItemName.climb]],
LocationName.strawberry_11: [[ItemName.ground_dash], LocationName.strawberry_11: [[ItemName.ground_dash],
[ItemName.air_dash], [ItemName.air_dash],
[ItemName.skid_jump]], [ItemName.skid_jump]],
LocationName.strawberry_12: [[ItemName.feather],
[ItemName.ground_dash],
[ItemName.air_dash]],
LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash], LocationName.strawberry_13: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]], [ItemName.breakables, ItemName.air_dash]],
LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash], LocationName.strawberry_14: [[ItemName.feather, ItemName.air_dash],
[ItemName.air_dash, ItemName.climb]], [ItemName.air_dash, ItemName.climb],
[ItemName.double_dash_refill, ItemName.air_dash]],
LocationName.strawberry_15: [[ItemName.feather], LocationName.strawberry_15: [[ItemName.feather],
[ItemName.ground_dash, ItemName.air_dash]], [ItemName.ground_dash, ItemName.air_dash]],
LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]], LocationName.strawberry_17: [[ItemName.double_dash_refill, ItemName.traffic_block]],
@@ -287,42 +107,94 @@ location_hard_moves_logic: Dict[str, List[List[str]]] = {
[ItemName.cassette, ItemName.feather, ItemName.climb]], [ItemName.cassette, ItemName.feather, ItemName.climb]],
LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump], LocationName.strawberry_29: [[ItemName.cassette, ItemName.dash_refill, ItemName.air_dash, ItemName.skid_jump],
[ItemName.cassette, ItemName.ground_dash, ItemName.air_dash]], [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], LocationName.strawberry_30: [[ItemName.cassette, ItemName.dash_refill, ItemName.double_dash_refill, ItemName.traffic_block, ItemName.breakables, 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.air_dash, ItemName.climb]],
[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], LocationName.sign_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_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], LocationName.car_2: [[ItemName.breakables, ItemName.ground_dash],
[ItemName.breakables, ItemName.air_dash]], [ItemName.breakables, ItemName.air_dash]],
} }
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool: region_standard_moves_logic: Dict[Tuple[str], List[List[str]]] = {
(RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]],
(RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]],
(RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]],
(RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]],
(RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]],
(RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]],
(RegionName.intro_islands, RegionName.granny_island): [[ItemName.ground_dash],
[ItemName.air_dash],
[ItemName.skid_jump],
[ItemName.climb]],
(RegionName.granny_island, RegionName.highway_island): [[ItemName.air_dash, ItemName.dash_refill]],
(RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]],
(RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.climb, ItemName.dash_refill]],
(RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill]],
(RegionName.highway_island, RegionName.granny_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.dash_refill]],
(RegionName.highway_island, RegionName.ne_feathers_island): [[ItemName.feather]],
(RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.cannot_access]],
(RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block]],
(RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.climb, ItemName.air_dash, ItemName.feather]],
(RegionName.se_house_island, RegionName.granny_island): [[ItemName.air_dash, ItemName.traffic_block, ItemName.double_dash_refill]],
(RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash, ItemName.double_dash_refill]],
(RegionName.badeline_tower_lower, RegionName.se_house_island): [[ItemName.cannot_access]],
(RegionName.badeline_tower_lower, RegionName.ne_feathers_island): [[ItemName.air_dash, ItemName.breakables, ItemName.feather]],
(RegionName.badeline_tower_lower, RegionName.granny_island): [[ItemName.cannot_access]],
(RegionName.badeline_tower_lower, RegionName.badeline_tower_upper): [[ItemName.cannot_access]],
(RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.double_dash_refill, ItemName.feather, ItemName.traffic_block, ItemName.breakables]],
(RegionName.badeline_tower_upper, RegionName.se_house_island): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.badeline_tower_upper, RegionName.ne_feathers_island): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.badeline_tower_upper, RegionName.granny_island): [[ItemName.dash_refill]],
(RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]],
}
region_hard_moves_logic: Dict[Tuple[str], List[List[str]]] = {
(RegionName.forsaken_city, RegionName.granny_island): [[ItemName.checkpoint_2], [ItemName.checkpoint_3], [ItemName.checkpoint_4]],
(RegionName.forsaken_city, RegionName.highway_island): [[ItemName.checkpoint_5], [ItemName.checkpoint_6]],
(RegionName.forsaken_city, RegionName.ne_feathers_island): [[ItemName.checkpoint_7]],
(RegionName.forsaken_city, RegionName.se_house_island): [[ItemName.checkpoint_8]],
(RegionName.forsaken_city, RegionName.badeline_tower_upper): [[ItemName.checkpoint_9]],
(RegionName.forsaken_city, RegionName.badeline_island): [[ItemName.checkpoint_10]],
(RegionName.granny_island, RegionName.nw_girders_island): [[ItemName.traffic_block]],
(RegionName.granny_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.granny_island, RegionName.se_house_island): [[ItemName.air_dash, ItemName.double_dash_refill], [ItemName.ground_dash]],
(RegionName.highway_island, RegionName.nw_girders_island): [[ItemName.air_dash, ItemName.ground_dash]],
(RegionName.nw_girders_island, RegionName.highway_island): [[ItemName.traffic_block], [ItemName.air_dash, ItemName.ground_dash]],
(RegionName.ne_feathers_island, RegionName.highway_island): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash], [ItemName.skid_jump]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_lower): [[ItemName.feather], [ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.ne_feathers_island, RegionName.badeline_tower_upper): [[ItemName.feather]],
(RegionName.se_house_island, RegionName.granny_island): [[ItemName.traffic_block]],
(RegionName.se_house_island, RegionName.badeline_tower_lower): [[ItemName.air_dash], [ItemName.ground_dash]],
(RegionName.badeline_tower_upper, RegionName.badeline_island): [[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.traffic_block],
[ItemName.air_dash, ItemName.climb, ItemName.feather, ItemName.skid_jump],
[ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.traffic_block],
[ItemName.air_dash, ItemName.climb, ItemName.ground_dash, ItemName.skid_jump]],
(RegionName.badeline_island, RegionName.badeline_tower_upper): [[ItemName.air_dash], [ItemName.ground_dash]],
}
def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bool:
if loc not in world.active_logic_mapping: if loc not in world.active_logic_mapping:
return True return True
@@ -332,12 +204,28 @@ def location_rule(state: CollectionState, world: Celeste64World, loc: str) -> bo
return False return False
def goal_rule(state: CollectionState, world: Celeste64World) -> bool: def region_connection_rule(state: CollectionState, world: Celeste64World, region_connection: Tuple[str]) -> bool:
if not state.has(ItemName.strawberry, world.player, world.strawberries_required): if region_connection not in world.active_region_logic_mapping:
return False return True
for possible_access in world.goal_logic_mapping: for possible_access in world.active_region_logic_mapping[region_connection]:
if state.has_all(possible_access, world.player): if state.has_all(possible_access, world.player):
return True return True
return False return False
def goal_rule(state: CollectionState, world: Celeste64World) -> bool:
if not state.has(ItemName.strawberry, world.player, world.strawberries_required):
return False
goal_region: Region = world.multiworld.get_region(RegionName.badeline_island, world.player)
return state.can_reach(goal_region)
def connect_region(world: Celeste64World, region: Region, dest_regions: List[str]):
rules: Dict[str, Callable[[CollectionState], bool]] = {}
for dest_region in dest_regions:
region_connection: Tuple[str] = (region.name, dest_region)
rules[dest_region] = lambda state, region_connection=region_connection: region_connection_rule(state, world, region_connection)
region.add_exits(dest_regions, rules)

View File

@@ -1,13 +1,15 @@
from copy import deepcopy from copy import deepcopy
from typing import Dict, List from typing import Dict, List, Tuple
from BaseClasses import ItemClassification, Location, Region, Tutorial from BaseClasses import ItemClassification, Location, Region, Tutorial
from worlds.AutoWorld import WebWorld, World from worlds.AutoWorld import WebWorld, World
from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table, item_table from .Items import Celeste64Item, unlockable_item_data_table, move_item_data_table, item_data_table,\
checkpoint_item_data_table, item_table
from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\ from .Locations import Celeste64Location, strawberry_location_data_table, friend_location_data_table,\
sign_location_data_table, car_location_data_table, location_table sign_location_data_table, car_location_data_table, checkpoint_location_data_table,\
location_table
from .Names import ItemName, LocationName from .Names import ItemName, LocationName
from .Options import Celeste64Options, celeste_64_option_groups from .Options import Celeste64Options, celeste_64_option_groups, resolve_options
class Celeste64WebWorld(WebWorld): class Celeste64WebWorld(WebWorld):
@@ -42,8 +44,15 @@ class Celeste64World(World):
# Instance Data # Instance Data
strawberries_required: int strawberries_required: int
active_logic_mapping: Dict[str, List[List[str]]] active_logic_mapping: Dict[str, List[List[str]]]
goal_logic_mapping: Dict[str, List[List[str]]] active_region_logic_mapping: Dict[Tuple[str], List[List[str]]]
madeline_one_dash_hair_color: int
madeline_two_dash_hair_color: int
madeline_no_dash_hair_color: int
madeline_feather_hair_color: int
def generate_early(self) -> None:
resolve_options(self)
def create_item(self, name: str) -> Celeste64Item: def create_item(self, name: str) -> Celeste64Item:
# Only make required amount of strawberries be Progression # Only make required amount of strawberries be Progression
@@ -76,25 +85,49 @@ class Celeste64World(World):
for name in unlockable_item_data_table.keys() for name in unlockable_item_data_table.keys()
if name not in self.options.start_inventory] if name not in self.options.start_inventory]
if self.options.move_shuffle: chosen_start_item: str = ""
move_items_for_itempool: List[str] = deepcopy(list(move_item_data_table.keys()))
if self.options.move_shuffle:
if self.options.logic_difficulty == "standard": if self.options.logic_difficulty == "standard":
# If the start_inventory already includes a move, don't worry about giving it one possible_unwalls: List[str] = [name for name in move_item_data_table.keys()
if not [move for move in move_items_for_itempool if move in self.options.start_inventory]: if name != ItemName.skid_jump]
chosen_start_move = self.random.choice(move_items_for_itempool)
move_items_for_itempool.remove(chosen_start_move) if self.options.checkpointsanity:
possible_unwalls.extend([name for name in checkpoint_item_data_table.keys()
if name != ItemName.checkpoint_1 and name != ItemName.checkpoint_10])
# If the start_inventory already includes a move or checkpoint, don't worry about giving it one
if not [item for item in possible_unwalls if item in self.multiworld.precollected_items[self.player]]:
chosen_start_item = self.random.choice(possible_unwalls)
if self.options.carsanity: if self.options.carsanity:
intro_car_loc: Location = self.multiworld.get_location(LocationName.car_1, self.player) 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)) intro_car_loc.place_locked_item(self.create_item(chosen_start_item))
location_count -= 1 location_count -= 1
else: else:
self.multiworld.push_precollected(self.create_item(chosen_start_move)) self.multiworld.push_precollected(self.create_item(chosen_start_item))
item_pool += [self.create_item(name) item_pool += [self.create_item(name)
for name in move_items_for_itempool for name in move_item_data_table.keys()
if name not in self.options.start_inventory] if name not in self.multiworld.precollected_items[self.player]
and name != chosen_start_item]
else:
for start_move in move_item_data_table.keys():
self.multiworld.push_precollected(self.create_item(start_move))
if self.options.checkpointsanity:
location_count += 9
goal_checkpoint_loc: Location = self.multiworld.get_location(LocationName.checkpoint_10, self.player)
goal_checkpoint_loc.place_locked_item(self.create_item(ItemName.checkpoint_10))
item_pool += [self.create_item(name)
for name in checkpoint_item_data_table.keys()
if name not in self.multiworld.precollected_items[self.player]
and name != ItemName.checkpoint_10
and name != chosen_start_item]
else:
for item_name in checkpoint_item_data_table.keys():
checkpoint_loc: Location = self.multiworld.get_location(item_name, self.player)
checkpoint_loc.place_locked_item(self.create_item(item_name))
real_total_strawberries: int = min(self.options.total_strawberries.value, location_count - len(item_pool)) 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)) self.strawberries_required = int(real_total_strawberries * (self.options.strawberries_required_percentage / 100))
@@ -140,18 +173,23 @@ class Celeste64World(World):
if location_data.region == region_name if location_data.region == region_name
}, Celeste64Location) }, Celeste64Location)
region.add_exits(region_data_table[region_name].connecting_regions) region.add_locations({
location_name: location_data.address for location_name, location_data in checkpoint_location_data_table.items()
if location_data.region == region_name
}, Celeste64Location)
from .Rules import connect_region
connect_region(self, region, region_data_table[region_name].connecting_regions)
# Have to do this here because of other games using State in a way that's bad
from .Rules import set_rules
set_rules(self)
def get_filler_item_name(self) -> str: def get_filler_item_name(self) -> str:
return ItemName.raspberry return ItemName.raspberry
def set_rules(self) -> None:
from .Rules import set_rules
set_rules(self)
def fill_slot_data(self): def fill_slot_data(self):
return { return {
"death_link": self.options.death_link.value, "death_link": self.options.death_link.value,
@@ -161,6 +199,11 @@ class Celeste64World(World):
"friendsanity": self.options.friendsanity.value, "friendsanity": self.options.friendsanity.value,
"signsanity": self.options.signsanity.value, "signsanity": self.options.signsanity.value,
"carsanity": self.options.carsanity.value, "carsanity": self.options.carsanity.value,
"checkpointsanity": self.options.checkpointsanity.value,
"madeline_one_dash_hair_color": self.madeline_one_dash_hair_color,
"madeline_two_dash_hair_color": self.madeline_two_dash_hair_color,
"madeline_no_dash_hair_color": self.madeline_no_dash_hair_color,
"madeline_feather_hair_color": self.madeline_feather_hair_color,
"badeline_chaser_source": self.options.badeline_chaser_source.value, "badeline_chaser_source": self.options.badeline_chaser_source.value,
"badeline_chaser_frequency": self.options.badeline_chaser_frequency.value, "badeline_chaser_frequency": self.options.badeline_chaser_frequency.value,
"badeline_chaser_speed": self.options.badeline_chaser_speed.value, "badeline_chaser_speed": self.options.badeline_chaser_speed.value,

View File

@@ -1,9 +1,10 @@
from dataclasses import dataclass from dataclasses import dataclass
import os import os
import io
from typing import TYPE_CHECKING, Dict, List, Optional, cast from typing import TYPE_CHECKING, Dict, List, Optional, cast
import zipfile import zipfile
from BaseClasses import Location from BaseClasses import Location
from worlds.Files import APContainer from worlds.Files import APContainer, AutoPatchRegister
from .Enum import CivVICheckType from .Enum import CivVICheckType
from .Locations import CivVILocation, CivVILocationData from .Locations import CivVILocation, CivVILocationData
@@ -25,24 +26,32 @@ class CivTreeItem:
ui_tree_row: int ui_tree_row: int
class CivVIContainer(APContainer): class CivVIContainer(APContainer, metaclass=AutoPatchRegister):
""" """
Responsible for generating the dynamic mod files for the Civ VI multiworld Responsible for generating the dynamic mod files for the Civ VI multiworld
""" """
game: Optional[str] = "Civilization VI" game: Optional[str] = "Civilization VI"
patch_file_ending = ".apcivvi"
def __init__(self, patch_data: Dict[str, str], base_path: str, output_directory: str, def __init__(self, patch_data: Dict[str, str] | io.BytesIO, base_path: str = "", output_directory: str = "",
player: Optional[int] = None, player_name: str = "", server: str = ""): player: Optional[int] = None, player_name: str = "", server: str = ""):
self.patch_data = patch_data if isinstance(patch_data, io.BytesIO):
self.file_path = base_path super().__init__(patch_data, player, player_name, server)
container_path = os.path.join(output_directory, base_path + ".apcivvi") else:
super().__init__(container_path, player, player_name, server) self.patch_data = patch_data
self.file_path = base_path
container_path = os.path.join(output_directory, base_path + ".apcivvi")
super().__init__(container_path, player, player_name, server)
def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None:
for filename, yml in self.patch_data.items(): for filename, yml in self.patch_data.items():
opened_zipfile.writestr(filename, yml) opened_zipfile.writestr(filename, yml)
super().write_contents(opened_zipfile) super().write_contents(opened_zipfile)
def sanitize_value(value: str) -> str:
"""Removes values that can cause issues in XML"""
return value.replace('"', "'").replace('&', 'and')
def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int: def get_cost(world: 'CivVIWorld', location: CivVILocationData) -> int:
""" """
@@ -58,7 +67,7 @@ def get_formatted_player_name(world: 'CivVIWorld', player: int) -> str:
Returns the name of the player in the world Returns the name of the player in the world
""" """
if player != world.player: if player != world.player:
return f"{world.multiworld.player_name[player]}{apo}s" return sanitize_value(f"{world.multiworld.player_name[player]}{apo}s")
return "Your" return "Your"
@@ -101,7 +110,7 @@ def generate_new_items(world: 'CivVIWorld') -> str:
<Row TechnologyType="TECH_BLOCKER" Name="TECH_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Tech created to prevent players from researching their own tech. If you can read this, then congrats you have reached the end of your tree before beating the game!"/> <Row TechnologyType="TECH_BLOCKER" Name="TECH_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Tech created to prevent players from researching their own tech. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row TechnologyType="{location.name}" ' {"".join([f'{tab}<Row TechnologyType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} ' f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{location.item.name}" ' f'{sanitize_value(location.item.name)}" '
f'EraType="{world.location_table[location.name].era_type}" ' f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" ' f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" ' f'Cost="{get_cost(world, world.location_table[location.name])}" '
@@ -117,7 +126,7 @@ def generate_new_items(world: 'CivVIWorld') -> str:
<Row CivicType="CIVIC_BLOCKER" Name="CIVIC_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Civic created to prevent players from researching their own civics. If you can read this, then congrats you have reached the end of your tree before beating the game!"/> <Row CivicType="CIVIC_BLOCKER" Name="CIVIC_BLOCKER" EraType="ERA_ANCIENT" UITreeRow="0" Cost="99999" AdvisorType="ADVISOR_GENERIC" Description="Archipelago Civic created to prevent players from researching their own civics. If you can read this, then congrats you have reached the end of your tree before beating the game!"/>
{"".join([f'{tab}<Row CivicType="{location.name}" ' {"".join([f'{tab}<Row CivicType="{location.name}" '
f'Name="{get_formatted_player_name(world, location.item.player)} ' f'Name="{get_formatted_player_name(world, location.item.player)} '
f'{location.item.name}" ' f'{sanitize_value(location.item.name)}" '
f'EraType="{world.location_table[location.name].era_type}" ' f'EraType="{world.location_table[location.name].era_type}" '
f'UITreeRow="{world.location_table[location.name].uiTreeRow}" ' f'UITreeRow="{world.location_table[location.name].uiTreeRow}" '
f'Cost="{get_cost(world, world.location_table[location.name])}" ' f'Cost="{get_cost(world, world.location_table[location.name])}" '

View File

View File

View File

@@ -644,6 +644,9 @@ class CV64PatchExtensions(APPatchExtension):
# Replace the PowerUp in the Forest Special1 Bridge 3HB rock with an L jewel if 3HBs aren't randomized # Replace the PowerUp in the Forest Special1 Bridge 3HB rock with an L jewel if 3HBs aren't randomized
if not options["multi_hit_breakables"]: if not options["multi_hit_breakables"]:
rom_data.write_byte(0x10C7A1, 0x03) rom_data.write_byte(0x10C7A1, 0x03)
# Replace the PowerUp in one of the lizard lockers if the lizard locker items aren't randomized.
if not options["lizard_locker_items"]:
rom_data.write_byte(0xBFCA07, 0x03)
# Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other # Change the appearance of the Pot-Pourri to that of a larger PowerUp regardless of the above setting, so other
# game PermaUps are distinguishable. # game PermaUps are distinguishable.
rom_data.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00]) rom_data.write_int32s(0xEE558, [0x06005F08, 0x3FB00000, 0xFFFFFF00])
@@ -714,7 +717,11 @@ class CV64PatchExtensions(APPatchExtension):
rom_data.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab rom_data.write_int32(0x10CF38, 0x8000FF4D) # CT final room door slab
rom_data.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab rom_data.write_int32(0x10CF44, 0x1000FF4D) # CT Renon slab
rom_data.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier rom_data.write_int32(0x10C908, 0x0008FF4D) # Villa foyer chandelier
rom_data.write_byte(0x10CF37, 0x04) # pointer for CT final room door slab item data
# Change the pointer to the Clock Tower final room 3HB door slab drops to not share its values with those of the
# 3HB slab near Renon at the top of the room.
if options["multi_hit_breakables"]:
rom_data.write_byte(0x10CF37, 0x04)
# Once-per-frame gameplay checks # Once-per-frame gameplay checks
rom_data.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034 rom_data.write_int32(0x6C848, 0x080FF40D) # J 0x803FD034
@@ -1000,6 +1007,7 @@ def write_patch(world: "CV64World", patch: CV64ProcedurePatch, offset_data: Dict
"multi_hit_breakables": world.options.multi_hit_breakables.value, "multi_hit_breakables": world.options.multi_hit_breakables.value,
"drop_previous_sub_weapon": world.options.drop_previous_sub_weapon.value, "drop_previous_sub_weapon": world.options.drop_previous_sub_weapon.value,
"countdown": world.options.countdown.value, "countdown": world.options.countdown.value,
"lizard_locker_items": world.options.lizard_locker_items.value,
"shopsanity": world.options.shopsanity.value, "shopsanity": world.options.shopsanity.value,
"panther_dash": world.options.panther_dash.value, "panther_dash": world.options.panther_dash.value,
"big_toss": world.options.big_toss.value, "big_toss": world.options.big_toss.value,

View File

@@ -19,8 +19,8 @@ from worlds.AutoWorld import WebWorld, World
from .aesthetics import shuffle_sub_weapons, get_location_data, get_countdown_flags, populate_enemy_drops, \ from .aesthetics import shuffle_sub_weapons, get_location_data, get_countdown_flags, populate_enemy_drops, \
get_start_inventory_data get_start_inventory_data
from .rom import RomData, patch_rom, get_base_rom_path, CVCotMProcedurePatch, CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, \ from .rom import RomData, patch_rom, get_base_rom_path, CVCotMProcedurePatch, CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH
CVCOTM_VC_US_HASH # CVCOTM_VC_US_HASH
from .client import CastlevaniaCotMClient from .client import CastlevaniaCotMClient
@@ -29,7 +29,8 @@ class CVCotMSettings(settings.Group):
"""File name of the Castlevania CotM US rom""" """File name of the Castlevania CotM US rom"""
copy_to = "Castlevania - Circle of the Moon (USA).gba" copy_to = "Castlevania - Circle of the Moon (USA).gba"
description = "Castlevania CotM (US) ROM File" description = "Castlevania CotM (US) ROM File"
md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH] # md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
md5s = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]
rom_file: RomFile = RomFile(RomFile.copy_to) rom_file: RomFile = RomFile(RomFile.copy_to)

View File

View File

@@ -153,11 +153,10 @@ Advance Collection ROM; most notably the fact that the audio does not function w
which is currently a requirement to connect to a multiworld. This happens because all audio code was stripped which is currently a requirement to connect to a multiworld. This happens because all audio code was stripped
from the ROM, and all sound is instead played by the collection through external means. from the ROM, and all sound is instead played by the collection through external means.
For this reason, it is most recommended to obtain the ROM by dumping it from an original cartridge of the game that you legally own. The Wii U Virtual Console version does not work due to changes in the code in that version.
Though, the Advance Collection *can* still technically be an option if you cannot do that and don't mind the lack of sound.
The Wii U Virtual Console version is currently untested. If you happen to have purchased it before the Wii U eShop shut down, you can try Due to the reasons mentioned above, it is most recommended to obtain the ROM by dumping it from an original cartridge of the
dumping and playing with it. However, at the moment, we cannot guarantee that it will work well due to it being untested. game that you legally own. However, the Advance Collection *is* an option if you cannot do that and don't mind the lack of sound.
Regardless of which released ROM you intend to try playing with, the US version of the game is required. Regardless of which released ROM you intend to try playing with, the US version of the game is required.

View File

@@ -4,7 +4,7 @@
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest). - [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
- A Castlevania: Circle of the Moon ROM of the US version specifically. The Archipelago community cannot provide this. - A Castlevania: Circle of the Moon ROM of the US version specifically. The Archipelago community cannot provide this.
The Castlevania Advance Collection ROM can technically be used, but it has no audio. The Wii U Virtual Console ROM is untested. The Castlevania Advance Collection ROM can be used, but it has no audio. The Wii U Virtual Console ROM does not work.
- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later. - [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 or later.
### Configuring BizHawk ### Configuring BizHawk

View File

@@ -22,11 +22,9 @@ if TYPE_CHECKING:
CVCOTM_CT_US_HASH = "50a1089600603a94e15ecf287f8d5a1f" # Original GBA cartridge ROM CVCOTM_CT_US_HASH = "50a1089600603a94e15ecf287f8d5a1f" # Original GBA cartridge ROM
CVCOTM_AC_US_HASH = "87a1bd6577b6702f97a60fc55772ad74" # Castlevania Advance Collection ROM CVCOTM_AC_US_HASH = "87a1bd6577b6702f97a60fc55772ad74" # Castlevania Advance Collection ROM
CVCOTM_VC_US_HASH = "2cc38305f62b337281663bad8c901cf9" # Wii U Virtual Console ROM # CVCOTM_VC_US_HASH = "2cc38305f62b337281663bad8c901cf9" # Wii U Virtual Console ROM
# NOTE: The Wii U VC version is untested as of when this comment was written. I am only including its hash in case it # The Wii U VC version is not currently supported. See the Game Page for more info.
# does work. If someone who has it can confirm it does indeed work, this comment should be removed. If it doesn't, the
# hash should be removed in addition. See the Game Page for more information about supported versions.
ARCHIPELAGO_IDENTIFIER_START = 0x7FFF00 ARCHIPELAGO_IDENTIFIER_START = 0x7FFF00
ARCHIPELAGO_IDENTIFIER = "ARCHIPELAG03" ARCHIPELAGO_IDENTIFIER = "ARCHIPELAG03"
@@ -518,7 +516,8 @@ class CVCotMPatchExtensions(APPatchExtension):
class CVCotMProcedurePatch(APProcedurePatch, APTokenMixin): class CVCotMProcedurePatch(APProcedurePatch, APTokenMixin):
hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH] # hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]
hash = [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]
patch_file_ending: str = ".apcvcotm" patch_file_ending: str = ".apcvcotm"
result_file_ending: str = ".gba" result_file_ending: str = ".gba"
@@ -585,7 +584,8 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
basemd5 = hashlib.md5() basemd5 = hashlib.md5()
basemd5.update(base_rom_bytes) basemd5.update(base_rom_bytes)
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]: # if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH, CVCOTM_VC_US_HASH]:
if basemd5.hexdigest() not in [CVCOTM_CT_US_HASH, CVCOTM_AC_US_HASH]:
raise Exception("Supplied Base ROM does not match known MD5s for Castlevania: Circle of the Moon USA." raise Exception("Supplied Base ROM does not match known MD5s for Castlevania: Circle of the Moon USA."
"Get the correct game and version, then dump it.") "Get the correct game and version, then dump it.")
setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes) setattr(get_base_rom_bytes, "base_rom_bytes", base_rom_bytes)

View File

@@ -126,7 +126,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3, 'map': 3,
'index': 64, 'index': 64,
'doom_type': 2001, 'doom_type': 2001,
'region': "Toxin Refinery (E1M3) Main"}, 'region': "Toxin Refinery (E1M3) Start"},
351019: {'name': 'Toxin Refinery (E1M3) - Shotgun 2', 351019: {'name': 'Toxin Refinery (E1M3) - Shotgun 2',
'episode': 1, 'episode': 1,
'map': 3, 'map': 3,
@@ -234,7 +234,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 107, 'index': 107,
'doom_type': 8, 'doom_type': 8,
'region': "Command Control (E1M4) Main"}, 'region': "Command Control (E1M4) Start"},
351037: {'name': 'Command Control (E1M4) - Shotgun', 351037: {'name': 'Command Control (E1M4) - Shotgun',
'episode': 1, 'episode': 1,
'map': 4, 'map': 4,
@@ -504,7 +504,7 @@ location_table: Dict[int, LocationDict] = {
'map': 7, 'map': 7,
'index': 122, 'index': 122,
'doom_type': 2001, 'doom_type': 2001,
'region': "Computer Station (E1M7) Main"}, 'region': "Computer Station (E1M7) Start"},
351082: {'name': 'Computer Station (E1M7) - Rocket launcher', 351082: {'name': 'Computer Station (E1M7) - Rocket launcher',
'episode': 1, 'episode': 1,
'map': 7, 'map': 7,
@@ -912,7 +912,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 109, 'index': 109,
'doom_type': 2001, 'doom_type': 2001,
'region': "Deimos Lab (E2M4) Main"}, 'region': "Deimos Lab (E2M4) Start"},
351150: {'name': 'Deimos Lab (E2M4) - Mega Armor', 351150: {'name': 'Deimos Lab (E2M4) - Mega Armor',
'episode': 2, 'episode': 2,
'map': 4, 'map': 4,
@@ -1242,7 +1242,7 @@ location_table: Dict[int, LocationDict] = {
'map': 8, 'map': 8,
'index': 36, 'index': 36,
'doom_type': 2019, 'doom_type': 2019,
'region': "Tower of Babel (E2M8) Main"}, 'region': "Tower of Babel (E2M8) Start"},
351205: {'name': 'Fortress of Mystery (E2M9) - Supercharge', 351205: {'name': 'Fortress of Mystery (E2M9) - Supercharge',
'episode': 2, 'episode': 2,
'map': 9, 'map': 9,
@@ -1638,7 +1638,7 @@ location_table: Dict[int, LocationDict] = {
'map': 5, 'map': 5,
'index': 187, 'index': 187,
'doom_type': 2001, 'doom_type': 2001,
'region': "Unholy Cathedral (E3M5) Main"}, 'region': "Unholy Cathedral (E3M5) Start"},
351271: {'name': 'Unholy Cathedral (E3M5) - Shotgun 2', 351271: {'name': 'Unholy Cathedral (E3M5) - Shotgun 2',
'episode': 3, 'episode': 3,
'map': 5, 'map': 5,

View File

@@ -33,9 +33,11 @@ regions:List[RegionDict] = [
# Toxin Refinery (E1M3) # Toxin Refinery (E1M3)
{"name":"Toxin Refinery (E1M3) Main", {"name":"Toxin Refinery (E1M3) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Toxin Refinery (E1M3) Blue","pro":False}]}, "connections":[
{"target":"Toxin Refinery (E1M3) Blue","pro":False},
{"target":"Toxin Refinery (E1M3) Start","pro":False}]},
{"name":"Toxin Refinery (E1M3) Blue", {"name":"Toxin Refinery (E1M3) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
@@ -46,15 +48,20 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Toxin Refinery (E1M3) Blue","pro":False}]}, "connections":[{"target":"Toxin Refinery (E1M3) Blue","pro":False}]},
{"name":"Toxin Refinery (E1M3) Start",
"connects_to_hub":True,
"episode":1,
"connections":[{"target":"Toxin Refinery (E1M3) Main","pro":False}]},
# Command Control (E1M4) # Command Control (E1M4)
{"name":"Command Control (E1M4) Main", {"name":"Command Control (E1M4) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[ "connections":[
{"target":"Command Control (E1M4) Blue","pro":False}, {"target":"Command Control (E1M4) Blue","pro":False},
{"target":"Command Control (E1M4) Yellow","pro":False}, {"target":"Command Control (E1M4) Yellow","pro":False},
{"target":"Command Control (E1M4) Ledge","pro":True}]}, {"target":"Command Control (E1M4) Ledge","pro":True},
{"target":"Command Control (E1M4) Start","pro":False}]},
{"name":"Command Control (E1M4) Blue", {"name":"Command Control (E1M4) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
@@ -72,6 +79,10 @@ regions:List[RegionDict] = [
{"target":"Command Control (E1M4) Main","pro":False}, {"target":"Command Control (E1M4) Main","pro":False},
{"target":"Command Control (E1M4) Blue","pro":False}, {"target":"Command Control (E1M4) Blue","pro":False},
{"target":"Command Control (E1M4) Yellow","pro":False}]}, {"target":"Command Control (E1M4) Yellow","pro":False}]},
{"name":"Command Control (E1M4) Start",
"connects_to_hub":True,
"episode":1,
"connections":[{"target":"Command Control (E1M4) Main","pro":False}]},
# Phobos Lab (E1M5) # Phobos Lab (E1M5)
{"name":"Phobos Lab (E1M5) Main", {"name":"Phobos Lab (E1M5) Main",
@@ -126,11 +137,12 @@ regions:List[RegionDict] = [
# Computer Station (E1M7) # Computer Station (E1M7)
{"name":"Computer Station (E1M7) Main", {"name":"Computer Station (E1M7) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[ "connections":[
{"target":"Computer Station (E1M7) Red","pro":False}, {"target":"Computer Station (E1M7) Red","pro":False},
{"target":"Computer Station (E1M7) Yellow","pro":False}]}, {"target":"Computer Station (E1M7) Yellow","pro":False},
{"target":"Computer Station (E1M7) Start","pro":False}]},
{"name":"Computer Station (E1M7) Blue", {"name":"Computer Station (E1M7) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
@@ -150,6 +162,10 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Computer Station (E1M7) Yellow","pro":False}]}, "connections":[{"target":"Computer Station (E1M7) Yellow","pro":False}]},
{"name":"Computer Station (E1M7) Start",
"connects_to_hub":True,
"episode":1,
"connections":[{"target":"Computer Station (E1M7) Main","pro":False}]},
# Phobos Anomaly (E1M8) # Phobos Anomaly (E1M8)
{"name":"Phobos Anomaly (E1M8) Main", {"name":"Phobos Anomaly (E1M8) Main",
@@ -238,9 +254,11 @@ regions:List[RegionDict] = [
# Deimos Lab (E2M4) # Deimos Lab (E2M4)
{"name":"Deimos Lab (E2M4) Main", {"name":"Deimos Lab (E2M4) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":2, "episode":2,
"connections":[{"target":"Deimos Lab (E2M4) Blue","pro":False}]}, "connections":[
{"target":"Deimos Lab (E2M4) Blue","pro":False},
{"target":"Deimos Lab (E2M4) Start","pro":False}]},
{"name":"Deimos Lab (E2M4) Blue", {"name":"Deimos Lab (E2M4) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":2, "episode":2,
@@ -251,6 +269,10 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":2, "episode":2,
"connections":[{"target":"Deimos Lab (E2M4) Blue","pro":False}]}, "connections":[{"target":"Deimos Lab (E2M4) Blue","pro":False}]},
{"name":"Deimos Lab (E2M4) Start",
"connects_to_hub":True,
"episode":2,
"connections":[{"target":"Deimos Lab (E2M4) Main","pro":False}]},
# Command Center (E2M5) # Command Center (E2M5)
{"name":"Command Center (E2M5) Main", {"name":"Command Center (E2M5) Main",
@@ -314,9 +336,13 @@ regions:List[RegionDict] = [
# Tower of Babel (E2M8) # Tower of Babel (E2M8)
{"name":"Tower of Babel (E2M8) Main", {"name":"Tower of Babel (E2M8) Main",
"connects_to_hub":False,
"episode":2,
"connections":[{"target":"Tower of Babel (E2M8) Start","pro":False}]},
{"name":"Tower of Babel (E2M8) Start",
"connects_to_hub":True, "connects_to_hub":True,
"episode":2, "episode":2,
"connections":[]}, "connections":[{"target":"Tower of Babel (E2M8) Main","pro":False}]},
# Fortress of Mystery (E2M9) # Fortress of Mystery (E2M9)
{"name":"Fortress of Mystery (E2M9) Main", {"name":"Fortress of Mystery (E2M9) Main",
@@ -392,11 +418,12 @@ regions:List[RegionDict] = [
# Unholy Cathedral (E3M5) # Unholy Cathedral (E3M5)
{"name":"Unholy Cathedral (E3M5) Main", {"name":"Unholy Cathedral (E3M5) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[ "connections":[
{"target":"Unholy Cathedral (E3M5) Yellow","pro":False}, {"target":"Unholy Cathedral (E3M5) Yellow","pro":False},
{"target":"Unholy Cathedral (E3M5) Blue","pro":False}]}, {"target":"Unholy Cathedral (E3M5) Blue","pro":False},
{"target":"Unholy Cathedral (E3M5) Start","pro":False}]},
{"name":"Unholy Cathedral (E3M5) Blue", {"name":"Unholy Cathedral (E3M5) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
@@ -405,6 +432,10 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"Unholy Cathedral (E3M5) Main","pro":False}]}, "connections":[{"target":"Unholy Cathedral (E3M5) Main","pro":False}]},
{"name":"Unholy Cathedral (E3M5) Start",
"connects_to_hub":True,
"episode":3,
"connections":[{"target":"Unholy Cathedral (E3M5) Main","pro":False}]},
# Mt. Erebus (E3M6) # Mt. Erebus (E3M6)
{"name":"Mt. Erebus (E3M6) Main", {"name":"Mt. Erebus (E3M6) Main",

View File

@@ -23,10 +23,6 @@ def set_episode1_rules(player, multiworld, pro):
state.has("Nuclear Plant (E1M2) - Red keycard", player, 1)) state.has("Nuclear Plant (E1M2) - Red keycard", player, 1))
# Toxin Refinery (E1M3) # Toxin Refinery (E1M3)
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(multiworld.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)) state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1))
set_rule(multiworld.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:
@@ -35,12 +31,13 @@ def set_episode1_rules(player, multiworld, pro):
state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1)) state.has("Toxin Refinery (E1M3) - Blue keycard", player, 1))
set_rule(multiworld.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)) state.has("Toxin Refinery (E1M3) - Yellow keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> Toxin Refinery (E1M3) Start", player), lambda state:
state.has("Toxin Refinery (E1M3)", player, 1))
set_rule(multiworld.get_entrance("Toxin Refinery (E1M3) Start -> Toxin Refinery (E1M3) Main", player), lambda state:
state.has("Shotgun", player, 1) or
state.has("Chaingun", player, 1))
# Command Control (E1M4) # Command Control (E1M4)
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(multiworld.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) - Blue keycard", player, 1) or
state.has("Command Control (E1M4) - Yellow keycard", player, 1)) state.has("Command Control (E1M4) - Yellow keycard", player, 1))
@@ -50,6 +47,11 @@ def set_episode1_rules(player, multiworld, pro):
set_rule(multiworld.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) - Yellow keycard", player, 1) or
state.has("Command Control (E1M4) - Blue keycard", player, 1)) state.has("Command Control (E1M4) - Blue keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> Command Control (E1M4) Start", player), lambda state:
state.has("Command Control (E1M4)", player, 1))
set_rule(multiworld.get_entrance("Command Control (E1M4) Start -> Command Control (E1M4) Main", player), lambda state:
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1))
# Phobos Lab (E1M5) # Phobos Lab (E1M5)
set_rule(multiworld.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Phobos Lab (E1M5) Main", player), lambda state:
@@ -83,11 +85,6 @@ def set_episode1_rules(player, multiworld, pro):
state.has("Central Processing (E1M6) - Yellow keycard", player, 1)) state.has("Central Processing (E1M6) - Yellow keycard", player, 1))
# Computer Station (E1M7) # Computer Station (E1M7)
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(multiworld.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)) state.has("Computer Station (E1M7) - Red keycard", player, 1))
set_rule(multiworld.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:
@@ -103,6 +100,12 @@ def set_episode1_rules(player, multiworld, pro):
state.has("Computer Station (E1M7) - Red keycard", player, 1)) state.has("Computer Station (E1M7) - Red keycard", player, 1))
set_rule(multiworld.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)) state.has("Computer Station (E1M7) - Yellow keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> Computer Station (E1M7) Start", player), lambda state:
state.has("Computer Station (E1M7)", player, 1))
set_rule(multiworld.get_entrance("Computer Station (E1M7) Start -> Computer Station (E1M7) Main", player), lambda state:
state.has("Shotgun", player, 1) and
state.has("Rocket launcher", player, 1) and
state.has("Chaingun", player, 1))
# Phobos Anomaly (E1M8) # Phobos Anomaly (E1M8)
set_rule(multiworld.get_entrance("Hub -> Phobos Anomaly (E1M8) Start", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Phobos Anomaly (E1M8) Start", player), lambda state:
@@ -172,15 +175,16 @@ def set_episode2_rules(player, multiworld, pro):
state.has("Refinery (E2M3) - Blue keycard", player, 1)) state.has("Refinery (E2M3) - Blue keycard", player, 1))
# Deimos Lab (E2M4) # Deimos Lab (E2M4)
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(multiworld.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)) state.has("Deimos Lab (E2M4) - Blue keycard", player, 1))
set_rule(multiworld.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)) state.has("Deimos Lab (E2M4) - Yellow keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> Deimos Lab (E2M4) Start", player), lambda state:
state.has("Deimos Lab (E2M4)", player, 1))
set_rule(multiworld.get_entrance("Deimos Lab (E2M4) Start -> Deimos Lab (E2M4) Main", player), lambda state:
state.has("Shotgun", player, 1) and
state.has("Plasma gun", player, 1) and
state.has("Chaingun", player, 1))
# Command Center (E2M5) # Command Center (E2M5)
set_rule(multiworld.get_entrance("Hub -> Command Center (E2M5) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Command Center (E2M5) Main", player), lambda state:
@@ -238,11 +242,11 @@ def set_episode2_rules(player, multiworld, pro):
state.has("Spawning Vats (E2M7) - Red keycard", player, 1)) state.has("Spawning Vats (E2M7) - Red keycard", player, 1))
# Tower of Babel (E2M8) # Tower of Babel (E2M8)
set_rule(multiworld.get_entrance("Hub -> Tower of Babel (E2M8) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Tower of Babel (E2M8) Start", player), lambda state:
(state.has("Tower of Babel (E2M8)", player, 1) and state.has("Tower of Babel (E2M8)", player, 1))
state.has("Shotgun", player, 1) and set_rule(multiworld.get_entrance("Tower of Babel (E2M8) Start -> Tower of Babel (E2M8) Main", player), lambda state:
state.has("Chaingun", player, 1)) and (state.has("Chaingun", player, 1) and
(state.has("Rocket launcher", player, 1) or state.has("Shotgun", player, 1)) and (state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1))) state.has("BFG9000", player, 1)))
@@ -321,13 +325,6 @@ def set_episode3_rules(player, multiworld, pro):
state.has("House of Pain (E3M4) - Yellow skull key", player, 1)) state.has("House of Pain (E3M4) - Yellow skull key", player, 1))
# Unholy Cathedral (E3M5) # Unholy Cathedral (E3M5)
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(multiworld.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)) state.has("Unholy Cathedral (E3M5) - Yellow skull key", player, 1))
set_rule(multiworld.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:
@@ -336,6 +333,13 @@ def set_episode3_rules(player, multiworld, pro):
state.has("Unholy Cathedral (E3M5) - Blue skull key", player, 1)) state.has("Unholy Cathedral (E3M5) - Blue skull key", player, 1))
set_rule(multiworld.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)) state.has("Unholy Cathedral (E3M5) - Yellow skull key", player, 1))
set_rule(multiworld.get_entrance("Hub -> Unholy Cathedral (E3M5) Start", player), lambda state:
state.has("Unholy Cathedral (E3M5)", player, 1))
set_rule(multiworld.get_entrance("Unholy Cathedral (E3M5) Start -> Unholy Cathedral (E3M5) Main", player), lambda state:
(state.has("Chaingun", player, 1) and
state.has("Shotgun", player, 1)) and (state.has("Plasma gun", player, 1) or
state.has("Rocket launcher", player, 1) or
state.has("BFG9000", player, 1)))
# Mt. Erebus (E3M6) # Mt. Erebus (E3M6)
set_rule(multiworld.get_entrance("Hub -> Mt. Erebus (E3M6) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Mt. Erebus (E3M6) Main", player), lambda state:

View File

@@ -50,14 +50,14 @@ class DOOM1993World(World):
location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()} location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
location_name_groups = Locations.location_name_groups location_name_groups = Locations.location_name_groups
starting_level_for_episode: List[str] = [ starting_level_for_episode: Dict[int, str] = {
"Hangar (E1M1)", 1: "Hangar (E1M1)",
"Deimos Anomaly (E2M1)", 2: "Deimos Anomaly (E2M1)",
"Hell Keep (E3M1)", 3: "Hell Keep (E3M1)",
"Hell Beneath (E4M1)" 4: "Hell Beneath (E4M1)"
] }
boss_level_for_espidoes: List[str] = [ all_boss_levels: List[str] = [
"Phobos Anomaly (E1M8)", "Phobos Anomaly (E1M8)",
"Tower of Babel (E2M8)", "Tower of Babel (E2M8)",
"Dis (E3M8)", "Dis (E3M8)",
@@ -82,6 +82,7 @@ class DOOM1993World(World):
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, multiworld: MultiWorld, player: int):
self.included_episodes = [1, 1, 1, 0] self.included_episodes = [1, 1, 1, 0]
self.location_count = 0 self.location_count = 0
self.starting_levels = []
super().__init__(multiworld, player) super().__init__(multiworld, player)
@@ -99,6 +100,16 @@ class DOOM1993World(World):
if self.get_episode_count() == 0: if self.get_episode_count() == 0:
self.included_episodes[0] = 1 self.included_episodes[0] = 1
self.starting_levels = [level_name for (episode, level_name) in self.starting_level_for_episode.items()
if self.included_episodes[episode - 1]]
# Solo Episode 3 presents a problem, because Hell Keep has only two locations.
# We have to give the player Slough of Despair (E3M2), and also mark a weapon early.
if self.get_episode_count() == 1 and self.included_episodes[2]:
early_weapon = self.random.choice(["Shotgun", "Chaingun"])
self.multiworld.early_items[self.player][early_weapon] = 1
self.starting_levels.append("Slough of Despair (E3M2)")
def create_regions(self): def create_regions(self):
pro = self.options.pro.value pro = self.options.pro.value
@@ -152,7 +163,7 @@ class DOOM1993World(World):
def completion_rule(self, state: CollectionState): def completion_rule(self, state: CollectionState):
goal_levels = Maps.map_names goal_levels = Maps.map_names
if self.options.goal.value: if self.options.goal.value:
goal_levels = self.boss_level_for_espidoes goal_levels = self.all_boss_levels
for map_name in goal_levels: for map_name in goal_levels:
if map_name + " - Exit" not in self.location_name_to_id: if map_name + " - Exit" not in self.location_name_to_id:
@@ -201,7 +212,7 @@ class DOOM1993World(World):
if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]: if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]:
continue continue
count = item["count"] if item["name"] not in self.starting_level_for_episode else item["count"] - 1 count = item["count"] if item["name"] not in self.starting_levels else item["count"] - 1
itempool += [self.create_item(item["name"]) for _ in range(count)] itempool += [self.create_item(item["name"]) for _ in range(count)]
# Backpack(s) based on options # Backpack(s) based on options
@@ -232,9 +243,8 @@ class DOOM1993World(World):
self.location_count -= 1 self.location_count -= 1
# Give starting levels right away # Give starting levels right away
for i in range(len(self.included_episodes)): for map_name in self.starting_levels:
if self.included_episodes[i]: self.multiworld.push_precollected(self.create_item(map_name))
self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i]))
# Give Computer area maps if option selected # Give Computer area maps if option selected
if self.options.start_with_computer_area_maps.value: if self.options.start_with_computer_area_maps.value:

View File

@@ -412,7 +412,7 @@ item_table: Dict[int, ItemDict] = {
'map': 2}, 'map': 2},
360246: {'classification': ItemClassification.progression, 360246: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Barrels o Fun (MAP23) - Yellow skull key', 'name': "Barrels o' Fun (MAP23) - Yellow skull key",
'doom_type': 39, 'doom_type': 39,
'episode': 3, 'episode': 3,
'map': 3}, 'map': 3},
@@ -880,19 +880,19 @@ item_table: Dict[int, ItemDict] = {
'map': 2}, 'map': 2},
360466: {'classification': ItemClassification.progression, 360466: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Barrels o Fun (MAP23)', 'name': "Barrels o' Fun (MAP23)",
'doom_type': -1, 'doom_type': -1,
'episode': 3, 'episode': 3,
'map': 3}, 'map': 3},
360467: {'classification': ItemClassification.progression, 360467: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Barrels o Fun (MAP23) - Complete', 'name': "Barrels o' Fun (MAP23) - Complete",
'doom_type': -2, 'doom_type': -2,
'episode': 3, 'episode': 3,
'map': 3}, 'map': 3},
360468: {'classification': ItemClassification.filler, 360468: {'classification': ItemClassification.filler,
'count': 1, 'count': 1,
'name': 'Barrels o Fun (MAP23) - Computer area map', 'name': "Barrels o' Fun (MAP23) - Computer area map",
'doom_type': 2026, 'doom_type': 2026,
'episode': 3, 'episode': 3,
'map': 3}, 'map': 3},
@@ -1024,37 +1024,37 @@ item_table: Dict[int, ItemDict] = {
'map': 10}, 'map': 10},
360490: {'classification': ItemClassification.progression, 360490: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Wolfenstein2 (MAP31)', 'name': 'Wolfenstein (MAP31)',
'doom_type': -1, 'doom_type': -1,
'episode': 4, 'episode': 4,
'map': 1}, 'map': 1},
360491: {'classification': ItemClassification.progression, 360491: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Wolfenstein2 (MAP31) - Complete', 'name': 'Wolfenstein (MAP31) - Complete',
'doom_type': -2, 'doom_type': -2,
'episode': 4, 'episode': 4,
'map': 1}, 'map': 1},
360492: {'classification': ItemClassification.filler, 360492: {'classification': ItemClassification.filler,
'count': 1, 'count': 1,
'name': 'Wolfenstein2 (MAP31) - Computer area map', 'name': 'Wolfenstein (MAP31) - Computer area map',
'doom_type': 2026, 'doom_type': 2026,
'episode': 4, 'episode': 4,
'map': 1}, 'map': 1},
360493: {'classification': ItemClassification.progression, 360493: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Grosse2 (MAP32)', 'name': 'Grosse (MAP32)',
'doom_type': -1, 'doom_type': -1,
'episode': 4, 'episode': 4,
'map': 2}, 'map': 2},
360494: {'classification': ItemClassification.progression, 360494: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'Grosse2 (MAP32) - Complete', 'name': 'Grosse (MAP32) - Complete',
'doom_type': -2, 'doom_type': -2,
'episode': 4, 'episode': 4,
'map': 2}, 'map': 2},
360495: {'classification': ItemClassification.filler, 360495: {'classification': ItemClassification.filler,
'count': 1, 'count': 1,
'name': 'Grosse2 (MAP32) - Computer area map', 'name': 'Grosse (MAP32) - Computer area map',
'doom_type': 2026, 'doom_type': 2026,
'episode': 4, 'episode': 4,
'map': 2}, 'map': 2},
@@ -1087,9 +1087,9 @@ item_table: Dict[int, ItemDict] = {
item_name_groups: Dict[str, Set[str]] = { item_name_groups: Dict[str, Set[str]] = {
'Ammos': {'Box of bullets', 'Box of rockets', 'Box of shotgun shells', 'Energy cell pack', }, 'Ammos': {'Box of bullets', 'Box of rockets', 'Box of shotgun shells', 'Energy cell pack', },
'Computer area maps': {'Barrels o Fun (MAP23) - Computer area map', 'Bloodfalls (MAP25) - Computer area map', 'Circle of Death (MAP11) - Computer area map', 'Dead Simple (MAP07) - Computer area map', 'Downtown (MAP13) - Computer area map', 'Entryway (MAP01) - Computer area map', 'Gotcha! (MAP20) - Computer area map', 'Grosse2 (MAP32) - Computer area map', 'Icon of Sin (MAP30) - Computer area map', 'Industrial Zone (MAP15) - Computer area map', 'Monster Condo (MAP27) - Computer area map', 'Nirvana (MAP21) - Computer area map', 'Refueling Base (MAP10) - Computer area map', 'Suburbs (MAP16) - Computer area map', 'Tenements (MAP17) - Computer area map', 'The Abandoned Mines (MAP26) - Computer area map', 'The Catacombs (MAP22) - Computer area map', 'The Chasm (MAP24) - Computer area map', 'The Citadel (MAP19) - Computer area map', 'The Courtyard (MAP18) - Computer area map', 'The Crusher (MAP06) - Computer area map', 'The Factory (MAP12) - Computer area map', 'The Focus (MAP04) - Computer area map', 'The Gantlet (MAP03) - Computer area map', 'The Inmost Dens (MAP14) - Computer area map', 'The Living End (MAP29) - Computer area map', 'The Pit (MAP09) - Computer area map', 'The Spirit World (MAP28) - Computer area map', 'The Waste Tunnels (MAP05) - Computer area map', 'Tricks and Traps (MAP08) - Computer area map', 'Underhalls (MAP02) - Computer area map', 'Wolfenstein2 (MAP31) - Computer area map', }, 'Computer area maps': {"Barrels o' Fun (MAP23) - Computer area map", 'Bloodfalls (MAP25) - Computer area map', 'Circle of Death (MAP11) - Computer area map', 'Dead Simple (MAP07) - Computer area map', 'Downtown (MAP13) - Computer area map', 'Entryway (MAP01) - Computer area map', 'Gotcha! (MAP20) - Computer area map', 'Grosse (MAP32) - Computer area map', 'Icon of Sin (MAP30) - Computer area map', 'Industrial Zone (MAP15) - Computer area map', 'Monster Condo (MAP27) - Computer area map', 'Nirvana (MAP21) - Computer area map', 'Refueling Base (MAP10) - Computer area map', 'Suburbs (MAP16) - Computer area map', 'Tenements (MAP17) - Computer area map', 'The Abandoned Mines (MAP26) - Computer area map', 'The Catacombs (MAP22) - Computer area map', 'The Chasm (MAP24) - Computer area map', 'The Citadel (MAP19) - Computer area map', 'The Courtyard (MAP18) - Computer area map', 'The Crusher (MAP06) - Computer area map', 'The Factory (MAP12) - Computer area map', 'The Focus (MAP04) - Computer area map', 'The Gantlet (MAP03) - Computer area map', 'The Inmost Dens (MAP14) - Computer area map', 'The Living End (MAP29) - Computer area map', 'The Pit (MAP09) - Computer area map', 'The Spirit World (MAP28) - Computer area map', 'The Waste Tunnels (MAP05) - Computer area map', 'Tricks and Traps (MAP08) - Computer area map', 'Underhalls (MAP02) - Computer area map', 'Wolfenstein (MAP31) - Computer area map', },
'Keys': {'Barrels o Fun (MAP23) - Yellow skull key', 'Bloodfalls (MAP25) - Blue skull key', 'Circle of Death (MAP11) - Blue keycard', 'Circle of Death (MAP11) - Red keycard', 'Downtown (MAP13) - Blue keycard', 'Downtown (MAP13) - Red keycard', 'Downtown (MAP13) - Yellow keycard', 'Industrial Zone (MAP15) - Blue keycard', 'Industrial Zone (MAP15) - Red keycard', 'Industrial Zone (MAP15) - Yellow keycard', 'Monster Condo (MAP27) - Blue skull key', 'Monster Condo (MAP27) - Red skull key', 'Monster Condo (MAP27) - Yellow skull key', 'Nirvana (MAP21) - Blue skull key', 'Nirvana (MAP21) - Red skull key', 'Nirvana (MAP21) - Yellow skull key', 'Refueling Base (MAP10) - Blue keycard', 'Refueling Base (MAP10) - Yellow keycard', 'Suburbs (MAP16) - Blue skull key', 'Suburbs (MAP16) - Red skull key', 'Tenements (MAP17) - Blue keycard', 'Tenements (MAP17) - Red keycard', 'Tenements (MAP17) - Yellow skull key', 'The Abandoned Mines (MAP26) - Blue keycard', 'The Abandoned Mines (MAP26) - Red keycard', 'The Abandoned Mines (MAP26) - Yellow keycard', 'The Catacombs (MAP22) - Blue skull key', 'The Catacombs (MAP22) - Red skull key', 'The Chasm (MAP24) - Blue keycard', 'The Chasm (MAP24) - Red keycard', 'The Citadel (MAP19) - Blue skull key', 'The Citadel (MAP19) - Red skull key', 'The Citadel (MAP19) - Yellow skull key', 'The Courtyard (MAP18) - Blue skull key', 'The Courtyard (MAP18) - Yellow skull key', 'The Crusher (MAP06) - Blue keycard', 'The Crusher (MAP06) - Red keycard', 'The Crusher (MAP06) - Yellow keycard', 'The Factory (MAP12) - Blue keycard', 'The Factory (MAP12) - Yellow keycard', 'The Focus (MAP04) - Blue keycard', 'The Focus (MAP04) - Red keycard', 'The Focus (MAP04) - Yellow keycard', 'The Gantlet (MAP03) - Blue keycard', 'The Gantlet (MAP03) - Red keycard', 'The Inmost Dens (MAP14) - Blue skull key', 'The Inmost Dens (MAP14) - Red skull key', 'The Pit (MAP09) - Blue keycard', 'The Pit (MAP09) - Yellow keycard', 'The Spirit World (MAP28) - Red skull key', 'The Spirit World (MAP28) - Yellow skull key', 'The Waste Tunnels (MAP05) - Blue keycard', 'The Waste Tunnels (MAP05) - Red keycard', 'The Waste Tunnels (MAP05) - Yellow keycard', 'Tricks and Traps (MAP08) - Red skull key', 'Tricks and Traps (MAP08) - Yellow skull key', 'Underhalls (MAP02) - Blue keycard', 'Underhalls (MAP02) - Red keycard', }, 'Keys': {"Barrels o' Fun (MAP23) - Yellow skull key", 'Bloodfalls (MAP25) - Blue skull key', 'Circle of Death (MAP11) - Blue keycard', 'Circle of Death (MAP11) - Red keycard', 'Downtown (MAP13) - Blue keycard', 'Downtown (MAP13) - Red keycard', 'Downtown (MAP13) - Yellow keycard', 'Industrial Zone (MAP15) - Blue keycard', 'Industrial Zone (MAP15) - Red keycard', 'Industrial Zone (MAP15) - Yellow keycard', 'Monster Condo (MAP27) - Blue skull key', 'Monster Condo (MAP27) - Red skull key', 'Monster Condo (MAP27) - Yellow skull key', 'Nirvana (MAP21) - Blue skull key', 'Nirvana (MAP21) - Red skull key', 'Nirvana (MAP21) - Yellow skull key', 'Refueling Base (MAP10) - Blue keycard', 'Refueling Base (MAP10) - Yellow keycard', 'Suburbs (MAP16) - Blue skull key', 'Suburbs (MAP16) - Red skull key', 'Tenements (MAP17) - Blue keycard', 'Tenements (MAP17) - Red keycard', 'Tenements (MAP17) - Yellow skull key', 'The Abandoned Mines (MAP26) - Blue keycard', 'The Abandoned Mines (MAP26) - Red keycard', 'The Abandoned Mines (MAP26) - Yellow keycard', 'The Catacombs (MAP22) - Blue skull key', 'The Catacombs (MAP22) - Red skull key', 'The Chasm (MAP24) - Blue keycard', 'The Chasm (MAP24) - Red keycard', 'The Citadel (MAP19) - Blue skull key', 'The Citadel (MAP19) - Red skull key', 'The Citadel (MAP19) - Yellow skull key', 'The Courtyard (MAP18) - Blue skull key', 'The Courtyard (MAP18) - Yellow skull key', 'The Crusher (MAP06) - Blue keycard', 'The Crusher (MAP06) - Red keycard', 'The Crusher (MAP06) - Yellow keycard', 'The Factory (MAP12) - Blue keycard', 'The Factory (MAP12) - Yellow keycard', 'The Focus (MAP04) - Blue keycard', 'The Focus (MAP04) - Red keycard', 'The Focus (MAP04) - Yellow keycard', 'The Gantlet (MAP03) - Blue keycard', 'The Gantlet (MAP03) - Red keycard', 'The Inmost Dens (MAP14) - Blue skull key', 'The Inmost Dens (MAP14) - Red skull key', 'The Pit (MAP09) - Blue keycard', 'The Pit (MAP09) - Yellow keycard', 'The Spirit World (MAP28) - Red skull key', 'The Spirit World (MAP28) - Yellow skull key', 'The Waste Tunnels (MAP05) - Blue keycard', 'The Waste Tunnels (MAP05) - Red keycard', 'The Waste Tunnels (MAP05) - Yellow keycard', 'Tricks and Traps (MAP08) - Red skull key', 'Tricks and Traps (MAP08) - Yellow skull key', 'Underhalls (MAP02) - Blue keycard', 'Underhalls (MAP02) - Red keycard', },
'Levels': {'Barrels o Fun (MAP23)', 'Bloodfalls (MAP25)', 'Circle of Death (MAP11)', 'Dead Simple (MAP07)', 'Downtown (MAP13)', 'Entryway (MAP01)', 'Gotcha! (MAP20)', 'Grosse2 (MAP32)', 'Icon of Sin (MAP30)', 'Industrial Zone (MAP15)', 'Monster Condo (MAP27)', 'Nirvana (MAP21)', 'Refueling Base (MAP10)', 'Suburbs (MAP16)', 'Tenements (MAP17)', 'The Abandoned Mines (MAP26)', 'The Catacombs (MAP22)', 'The Chasm (MAP24)', 'The Citadel (MAP19)', 'The Courtyard (MAP18)', 'The Crusher (MAP06)', 'The Factory (MAP12)', 'The Focus (MAP04)', 'The Gantlet (MAP03)', 'The Inmost Dens (MAP14)', 'The Living End (MAP29)', 'The Pit (MAP09)', 'The Spirit World (MAP28)', 'The Waste Tunnels (MAP05)', 'Tricks and Traps (MAP08)', 'Underhalls (MAP02)', 'Wolfenstein2 (MAP31)', }, 'Levels': {"Barrels o' Fun (MAP23)", 'Bloodfalls (MAP25)', 'Circle of Death (MAP11)', 'Dead Simple (MAP07)', 'Downtown (MAP13)', 'Entryway (MAP01)', 'Gotcha! (MAP20)', 'Grosse (MAP32)', 'Icon of Sin (MAP30)', 'Industrial Zone (MAP15)', 'Monster Condo (MAP27)', 'Nirvana (MAP21)', 'Refueling Base (MAP10)', 'Suburbs (MAP16)', 'Tenements (MAP17)', 'The Abandoned Mines (MAP26)', 'The Catacombs (MAP22)', 'The Chasm (MAP24)', 'The Citadel (MAP19)', 'The Courtyard (MAP18)', 'The Crusher (MAP06)', 'The Factory (MAP12)', 'The Focus (MAP04)', 'The Gantlet (MAP03)', 'The Inmost Dens (MAP14)', 'The Living End (MAP29)', 'The Pit (MAP09)', 'The Spirit World (MAP28)', 'The Waste Tunnels (MAP05)', 'Tricks and Traps (MAP08)', 'Underhalls (MAP02)', 'Wolfenstein (MAP31)', },
'Powerups': {'Armor', 'Berserk', 'Invulnerability', 'Mega Armor', 'Megasphere', 'Partial invisibility', 'Supercharge', }, 'Powerups': {'Armor', 'Berserk', 'Invulnerability', 'Mega Armor', 'Megasphere', 'Partial invisibility', 'Supercharge', },
'Weapons': {'BFG9000', 'Chaingun', 'Chainsaw', 'Plasma gun', 'Rocket launcher', 'Shotgun', 'Super Shotgun', }, 'Weapons': {'BFG9000', 'Chaingun', 'Chainsaw', 'Plasma gun', 'Rocket launcher', 'Shotgun', 'Super Shotgun', },
} }

View File

@@ -180,7 +180,7 @@ location_table: Dict[int, LocationDict] = {
'map': 5, 'map': 5,
'index': 46, 'index': 46,
'doom_type': 82, 'doom_type': 82,
'region': "The Waste Tunnels (MAP05) Main"}, 'region': "The Waste Tunnels (MAP05) Start"},
361028: {'name': 'The Waste Tunnels (MAP05) - Blue keycard', 361028: {'name': 'The Waste Tunnels (MAP05) - Blue keycard',
'episode': 1, 'episode': 1,
'map': 5, 'map': 5,
@@ -234,7 +234,7 @@ location_table: Dict[int, LocationDict] = {
'map': 5, 'map': 5,
'index': 202, 'index': 202,
'doom_type': 2001, 'doom_type': 2001,
'region': "The Waste Tunnels (MAP05) Main"}, 'region': "The Waste Tunnels (MAP05) Start"},
361037: {'name': 'The Waste Tunnels (MAP05) - Berserk', 361037: {'name': 'The Waste Tunnels (MAP05) - Berserk',
'episode': 1, 'episode': 1,
'map': 5, 'map': 5,
@@ -360,7 +360,7 @@ location_table: Dict[int, LocationDict] = {
'map': 7, 'map': 7,
'index': 8, 'index': 8,
'doom_type': 82, 'doom_type': 82,
'region': "Dead Simple (MAP07) Main"}, 'region': "Dead Simple (MAP07) Start"},
361058: {'name': 'Dead Simple (MAP07) - Chaingun', 361058: {'name': 'Dead Simple (MAP07) - Chaingun',
'episode': 1, 'episode': 1,
'map': 7, 'map': 7,
@@ -378,7 +378,7 @@ location_table: Dict[int, LocationDict] = {
'map': 7, 'map': 7,
'index': 43, 'index': 43,
'doom_type': 8, 'doom_type': 8,
'region': "Dead Simple (MAP07) Main"}, 'region': "Dead Simple (MAP07) Start"},
361061: {'name': 'Dead Simple (MAP07) - Berserk', 361061: {'name': 'Dead Simple (MAP07) - Berserk',
'episode': 1, 'episode': 1,
'map': 7, 'map': 7,
@@ -570,7 +570,7 @@ location_table: Dict[int, LocationDict] = {
'map': 9, 'map': 9,
'index': 26, 'index': 26,
'doom_type': 2019, 'doom_type': 2019,
'region': "The Pit (MAP09) Main"}, 'region': "The Pit (MAP09) Start"},
361093: {'name': 'The Pit (MAP09) - Supercharge', 361093: {'name': 'The Pit (MAP09) - Supercharge',
'episode': 1, 'episode': 1,
'map': 9, 'map': 9,
@@ -678,7 +678,7 @@ location_table: Dict[int, LocationDict] = {
'map': 10, 'map': 10,
'index': 99, 'index': 99,
'doom_type': 2001, 'doom_type': 2001,
'region': "Refueling Base (MAP10) Main"}, 'region': "Refueling Base (MAP10) Start"},
361111: {'name': 'Refueling Base (MAP10) - Chaingun', 361111: {'name': 'Refueling Base (MAP10) - Chaingun',
'episode': 1, 'episode': 1,
'map': 10, 'map': 10,
@@ -846,31 +846,31 @@ location_table: Dict[int, LocationDict] = {
'map': 11, 'map': 11,
'index': 88, 'index': 88,
'doom_type': 8, 'doom_type': 8,
'region': "Circle of Death (MAP11) Red"}, 'region': "Circle of Death (MAP11) Ending"},
361139: {'name': 'Circle of Death (MAP11) - Supercharge 2', 361139: {'name': 'Circle of Death (MAP11) - Supercharge 2',
'episode': 1, 'episode': 1,
'map': 11, 'map': 11,
'index': 108, 'index': 108,
'doom_type': 2013, 'doom_type': 2013,
'region': "Circle of Death (MAP11) Red"}, 'region': "Circle of Death (MAP11) Ending"},
361140: {'name': 'Circle of Death (MAP11) - BFG9000', 361140: {'name': 'Circle of Death (MAP11) - BFG9000',
'episode': 1, 'episode': 1,
'map': 11, 'map': 11,
'index': 110, 'index': 110,
'doom_type': 2006, 'doom_type': 2006,
'region': "Circle of Death (MAP11) Red"}, 'region': "Circle of Death (MAP11) Ending"},
361141: {'name': 'Circle of Death (MAP11) - Exit', 361141: {'name': 'Circle of Death (MAP11) - Exit',
'episode': 1, 'episode': 1,
'map': 11, 'map': 11,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "Circle of Death (MAP11) Red"}, 'region': "Circle of Death (MAP11) Ending"},
361142: {'name': 'The Factory (MAP12) - Shotgun', 361142: {'name': 'The Factory (MAP12) - Shotgun',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
'index': 14, 'index': 14,
'doom_type': 2001, 'doom_type': 2001,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Outdoors"},
361143: {'name': 'The Factory (MAP12) - Berserk', 361143: {'name': 'The Factory (MAP12) - Berserk',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
@@ -888,13 +888,13 @@ location_table: Dict[int, LocationDict] = {
'map': 1, 'map': 1,
'index': 52, 'index': 52,
'doom_type': 2013, 'doom_type': 2013,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Indoors"},
361146: {'name': 'The Factory (MAP12) - Blue keycard', 361146: {'name': 'The Factory (MAP12) - Blue keycard',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
'index': 54, 'index': 54,
'doom_type': 5, 'doom_type': 5,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Indoors"},
361147: {'name': 'The Factory (MAP12) - Armor', 361147: {'name': 'The Factory (MAP12) - Armor',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
@@ -912,31 +912,31 @@ location_table: Dict[int, LocationDict] = {
'map': 1, 'map': 1,
'index': 83, 'index': 83,
'doom_type': 2013, 'doom_type': 2013,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Indoors"},
361150: {'name': 'The Factory (MAP12) - Armor 2', 361150: {'name': 'The Factory (MAP12) - Armor 2',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
'index': 92, 'index': 92,
'doom_type': 2018, 'doom_type': 2018,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Outdoors"},
361151: {'name': 'The Factory (MAP12) - Partial invisibility', 361151: {'name': 'The Factory (MAP12) - Partial invisibility',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
'index': 93, 'index': 93,
'doom_type': 2024, 'doom_type': 2024,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Outdoors"},
361152: {'name': 'The Factory (MAP12) - Berserk 2', 361152: {'name': 'The Factory (MAP12) - Berserk 2',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
'index': 107, 'index': 107,
'doom_type': 2023, 'doom_type': 2023,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Indoors"},
361153: {'name': 'The Factory (MAP12) - Yellow keycard', 361153: {'name': 'The Factory (MAP12) - Yellow keycard',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
'index': 123, 'index': 123,
'doom_type': 6, 'doom_type': 6,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Indoors"},
361154: {'name': 'The Factory (MAP12) - BFG9000', 361154: {'name': 'The Factory (MAP12) - BFG9000',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
@@ -954,7 +954,7 @@ location_table: Dict[int, LocationDict] = {
'map': 1, 'map': 1,
'index': 192, 'index': 192,
'doom_type': 82, 'doom_type': 82,
'region': "The Factory (MAP12) Main"}, 'region': "The Factory (MAP12) Indoors"},
361157: {'name': 'The Factory (MAP12) - Exit', 361157: {'name': 'The Factory (MAP12) - Exit',
'episode': 2, 'episode': 2,
'map': 1, 'map': 1,
@@ -1812,7 +1812,7 @@ location_table: Dict[int, LocationDict] = {
'map': 1, 'map': 1,
'index': 70, 'index': 70,
'doom_type': 82, 'doom_type': 82,
'region': "Nirvana (MAP21) Main"}, 'region': "Nirvana (MAP21) Start"},
361300: {'name': 'Nirvana (MAP21) - Rocket launcher', 361300: {'name': 'Nirvana (MAP21) - Rocket launcher',
'episode': 3, 'episode': 3,
'map': 1, 'map': 1,
@@ -1884,7 +1884,7 @@ location_table: Dict[int, LocationDict] = {
'map': 2, 'map': 2,
'index': 28, 'index': 28,
'doom_type': 2001, 'doom_type': 2001,
'region': "The Catacombs (MAP22) Main"}, 'region': "The Catacombs (MAP22) Early"},
361312: {'name': 'The Catacombs (MAP22) - Berserk', 361312: {'name': 'The Catacombs (MAP22) - Berserk',
'episode': 3, 'episode': 3,
'map': 2, 'map': 2,
@@ -1896,103 +1896,103 @@ location_table: Dict[int, LocationDict] = {
'map': 2, 'map': 2,
'index': 83, 'index': 83,
'doom_type': 2004, 'doom_type': 2004,
'region': "The Catacombs (MAP22) Main"}, 'region': "The Catacombs (MAP22) Early"},
361314: {'name': 'The Catacombs (MAP22) - Supercharge', 361314: {'name': 'The Catacombs (MAP22) - Supercharge',
'episode': 3, 'episode': 3,
'map': 2, 'map': 2,
'index': 118, 'index': 118,
'doom_type': 2013, 'doom_type': 2013,
'region': "The Catacombs (MAP22) Main"}, 'region': "The Catacombs (MAP22) Early"},
361315: {'name': 'The Catacombs (MAP22) - Armor', 361315: {'name': 'The Catacombs (MAP22) - Armor',
'episode': 3, 'episode': 3,
'map': 2, 'map': 2,
'index': 119, 'index': 119,
'doom_type': 2018, 'doom_type': 2018,
'region': "The Catacombs (MAP22) Main"}, 'region': "The Catacombs (MAP22) Early"},
361316: {'name': 'The Catacombs (MAP22) - Exit', 361316: {'name': 'The Catacombs (MAP22) - Exit',
'episode': 3, 'episode': 3,
'map': 2, 'map': 2,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "The Catacombs (MAP22) Red"}, 'region': "The Catacombs (MAP22) Red"},
361317: {'name': 'Barrels o Fun (MAP23) - Shotgun', 361317: {'name': "Barrels o' Fun (MAP23) - Shotgun",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 136, 'index': 136,
'doom_type': 2001, 'doom_type': 2001,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361318: {'name': 'Barrels o Fun (MAP23) - Berserk', 361318: {'name': "Barrels o' Fun (MAP23) - Berserk",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 222, 'index': 222,
'doom_type': 2023, 'doom_type': 2023,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361319: {'name': 'Barrels o Fun (MAP23) - Backpack', 361319: {'name': "Barrels o' Fun (MAP23) - Backpack",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 223, 'index': 223,
'doom_type': 8, 'doom_type': 8,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361320: {'name': 'Barrels o Fun (MAP23) - Computer area map', 361320: {'name': "Barrels o' Fun (MAP23) - Computer area map",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 224, 'index': 224,
'doom_type': 2026, 'doom_type': 2026,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361321: {'name': 'Barrels o Fun (MAP23) - Armor', 361321: {'name': "Barrels o' Fun (MAP23) - Armor",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 249, 'index': 249,
'doom_type': 2018, 'doom_type': 2018,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361322: {'name': 'Barrels o Fun (MAP23) - Rocket launcher', 361322: {'name': "Barrels o' Fun (MAP23) - Rocket launcher",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 264, 'index': 264,
'doom_type': 2003, 'doom_type': 2003,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361323: {'name': 'Barrels o Fun (MAP23) - Megasphere', 361323: {'name': "Barrels o' Fun (MAP23) - Megasphere",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 266, 'index': 266,
'doom_type': 83, 'doom_type': 83,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361324: {'name': 'Barrels o Fun (MAP23) - Supercharge', 361324: {'name': "Barrels o' Fun (MAP23) - Supercharge",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 277, 'index': 277,
'doom_type': 2013, 'doom_type': 2013,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361325: {'name': 'Barrels o Fun (MAP23) - Backpack 2', 361325: {'name': "Barrels o' Fun (MAP23) - Backpack 2",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 301, 'index': 301,
'doom_type': 8, 'doom_type': 8,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361326: {'name': 'Barrels o Fun (MAP23) - Yellow skull key', 361326: {'name': "Barrels o' Fun (MAP23) - Yellow skull key",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 307, 'index': 307,
'doom_type': 39, 'doom_type': 39,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361327: {'name': 'Barrels o Fun (MAP23) - BFG9000', 361327: {'name': "Barrels o' Fun (MAP23) - BFG9000",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': 342, 'index': 342,
'doom_type': 2006, 'doom_type': 2006,
'region': "Barrels o Fun (MAP23) Main"}, 'region': "Barrels o' Fun (MAP23) Main"},
361328: {'name': 'Barrels o Fun (MAP23) - Exit', 361328: {'name': "Barrels o' Fun (MAP23) - Exit",
'episode': 3, 'episode': 3,
'map': 3, 'map': 3,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "Barrels o Fun (MAP23) Yellow"}, 'region': "Barrels o' Fun (MAP23) Yellow"},
361329: {'name': 'The Chasm (MAP24) - Plasma gun', 361329: {'name': 'The Chasm (MAP24) - Plasma gun',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
'index': 5, 'index': 5,
'doom_type': 2004, 'doom_type': 2004,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361330: {'name': 'The Chasm (MAP24) - Shotgun', 361330: {'name': 'The Chasm (MAP24) - Shotgun',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
@@ -2004,7 +2004,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 12, 'index': 12,
'doom_type': 2022, 'doom_type': 2022,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361332: {'name': 'The Chasm (MAP24) - Rocket launcher', 361332: {'name': 'The Chasm (MAP24) - Rocket launcher',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
@@ -2022,7 +2022,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 31, 'index': 31,
'doom_type': 8, 'doom_type': 8,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361335: {'name': 'The Chasm (MAP24) - Berserk', 361335: {'name': 'The Chasm (MAP24) - Berserk',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
@@ -2034,19 +2034,19 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 155, 'index': 155,
'doom_type': 2023, 'doom_type': 2023,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361337: {'name': 'The Chasm (MAP24) - Armor', 361337: {'name': 'The Chasm (MAP24) - Armor',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
'index': 169, 'index': 169,
'doom_type': 2018, 'doom_type': 2018,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361338: {'name': 'The Chasm (MAP24) - Red keycard', 361338: {'name': 'The Chasm (MAP24) - Red keycard',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
'index': 261, 'index': 261,
'doom_type': 13, 'doom_type': 13,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361339: {'name': 'The Chasm (MAP24) - BFG9000', 361339: {'name': 'The Chasm (MAP24) - BFG9000',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
@@ -2064,7 +2064,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 355, 'index': 355,
'doom_type': 83, 'doom_type': 83,
'region': "The Chasm (MAP24) Main"}, 'region': "The Chasm (MAP24) Blue"},
361342: {'name': 'The Chasm (MAP24) - Megasphere 2', 361342: {'name': 'The Chasm (MAP24) - Megasphere 2',
'episode': 3, 'episode': 3,
'map': 4, 'map': 4,
@@ -2082,7 +2082,7 @@ location_table: Dict[int, LocationDict] = {
'map': 5, 'map': 5,
'index': 6, 'index': 6,
'doom_type': 82, 'doom_type': 82,
'region': "Bloodfalls (MAP25) Main"}, 'region': "Bloodfalls (MAP25) Start"},
361345: {'name': 'Bloodfalls (MAP25) - Partial invisibility', 361345: {'name': 'Bloodfalls (MAP25) - Partial invisibility',
'episode': 3, 'episode': 3,
'map': 5, 'map': 5,
@@ -2664,55 +2664,55 @@ location_table: Dict[int, LocationDict] = {
'map': 10, 'map': 10,
'index': 40, 'index': 40,
'doom_type': 2006, 'doom_type': 2006,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361442: {'name': 'Icon of Sin (MAP30) - Chaingun', 361442: {'name': 'Icon of Sin (MAP30) - Chaingun',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 41, 'index': 41,
'doom_type': 2002, 'doom_type': 2002,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361443: {'name': 'Icon of Sin (MAP30) - Chainsaw', 361443: {'name': 'Icon of Sin (MAP30) - Chainsaw',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 42, 'index': 42,
'doom_type': 2005, 'doom_type': 2005,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361444: {'name': 'Icon of Sin (MAP30) - Plasma gun', 361444: {'name': 'Icon of Sin (MAP30) - Plasma gun',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 43, 'index': 43,
'doom_type': 2004, 'doom_type': 2004,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361445: {'name': 'Icon of Sin (MAP30) - Rocket launcher', 361445: {'name': 'Icon of Sin (MAP30) - Rocket launcher',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 44, 'index': 44,
'doom_type': 2003, 'doom_type': 2003,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361446: {'name': 'Icon of Sin (MAP30) - Shotgun', 361446: {'name': 'Icon of Sin (MAP30) - Shotgun',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 45, 'index': 45,
'doom_type': 2001, 'doom_type': 2001,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361447: {'name': 'Icon of Sin (MAP30) - Super Shotgun', 361447: {'name': 'Icon of Sin (MAP30) - Super Shotgun',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 46, 'index': 46,
'doom_type': 82, 'doom_type': 82,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361448: {'name': 'Icon of Sin (MAP30) - Backpack', 361448: {'name': 'Icon of Sin (MAP30) - Backpack',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 47, 'index': 47,
'doom_type': 8, 'doom_type': 8,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361449: {'name': 'Icon of Sin (MAP30) - Megasphere', 361449: {'name': 'Icon of Sin (MAP30) - Megasphere',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
'index': 64, 'index': 64,
'doom_type': 83, 'doom_type': 83,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Start"},
361450: {'name': 'Icon of Sin (MAP30) - Megasphere 2', 361450: {'name': 'Icon of Sin (MAP30) - Megasphere 2',
'episode': 3, 'episode': 3,
'map': 10, 'map': 10,
@@ -2731,179 +2731,179 @@ location_table: Dict[int, LocationDict] = {
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "Icon of Sin (MAP30) Main"}, 'region': "Icon of Sin (MAP30) Main"},
361453: {'name': 'Wolfenstein2 (MAP31) - Rocket launcher', 361453: {'name': 'Wolfenstein (MAP31) - Rocket launcher',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 110, 'index': 110,
'doom_type': 2003, 'doom_type': 2003,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361454: {'name': 'Wolfenstein2 (MAP31) - Shotgun', 361454: {'name': 'Wolfenstein (MAP31) - Shotgun',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 139, 'index': 139,
'doom_type': 2001, 'doom_type': 2001,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361455: {'name': 'Wolfenstein2 (MAP31) - Berserk', 361455: {'name': 'Wolfenstein (MAP31) - Berserk',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 263, 'index': 263,
'doom_type': 2023, 'doom_type': 2023,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361456: {'name': 'Wolfenstein2 (MAP31) - Supercharge', 361456: {'name': 'Wolfenstein (MAP31) - Supercharge',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 278, 'index': 278,
'doom_type': 2013, 'doom_type': 2013,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361457: {'name': 'Wolfenstein2 (MAP31) - Chaingun', 361457: {'name': 'Wolfenstein (MAP31) - Chaingun',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 305, 'index': 305,
'doom_type': 2002, 'doom_type': 2002,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361458: {'name': 'Wolfenstein2 (MAP31) - Super Shotgun', 361458: {'name': 'Wolfenstein (MAP31) - Super Shotgun',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 308, 'index': 308,
'doom_type': 82, 'doom_type': 82,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361459: {'name': 'Wolfenstein2 (MAP31) - Partial invisibility', 361459: {'name': 'Wolfenstein (MAP31) - Partial invisibility',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 309, 'index': 309,
'doom_type': 2024, 'doom_type': 2024,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361460: {'name': 'Wolfenstein2 (MAP31) - Megasphere', 361460: {'name': 'Wolfenstein (MAP31) - Megasphere',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 310, 'index': 310,
'doom_type': 83, 'doom_type': 83,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361461: {'name': 'Wolfenstein2 (MAP31) - Backpack', 361461: {'name': 'Wolfenstein (MAP31) - Backpack',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 311, 'index': 311,
'doom_type': 8, 'doom_type': 8,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361462: {'name': 'Wolfenstein2 (MAP31) - Backpack 2', 361462: {'name': 'Wolfenstein (MAP31) - Backpack 2',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 312, 'index': 312,
'doom_type': 8, 'doom_type': 8,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361463: {'name': 'Wolfenstein2 (MAP31) - Backpack 3', 361463: {'name': 'Wolfenstein (MAP31) - Backpack 3',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 313, 'index': 313,
'doom_type': 8, 'doom_type': 8,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361464: {'name': 'Wolfenstein2 (MAP31) - Backpack 4', 361464: {'name': 'Wolfenstein (MAP31) - Backpack 4',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 314, 'index': 314,
'doom_type': 8, 'doom_type': 8,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361465: {'name': 'Wolfenstein2 (MAP31) - BFG9000', 361465: {'name': 'Wolfenstein (MAP31) - BFG9000',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 315, 'index': 315,
'doom_type': 2006, 'doom_type': 2006,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361466: {'name': 'Wolfenstein2 (MAP31) - Plasma gun', 361466: {'name': 'Wolfenstein (MAP31) - Plasma gun',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': 316, 'index': 316,
'doom_type': 2004, 'doom_type': 2004,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361467: {'name': 'Wolfenstein2 (MAP31) - Exit', 361467: {'name': 'Wolfenstein (MAP31) - Exit',
'episode': 4, 'episode': 4,
'map': 1, 'map': 1,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "Wolfenstein2 (MAP31) Main"}, 'region': "Wolfenstein (MAP31) Main"},
361468: {'name': 'Grosse2 (MAP32) - Plasma gun', 361468: {'name': 'Grosse (MAP32) - Plasma gun',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 33, 'index': 33,
'doom_type': 2004, 'doom_type': 2004,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361469: {'name': 'Grosse2 (MAP32) - Rocket launcher', 361469: {'name': 'Grosse (MAP32) - Rocket launcher',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 57, 'index': 57,
'doom_type': 2003, 'doom_type': 2003,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Start"},
361470: {'name': 'Grosse2 (MAP32) - Invulnerability', 361470: {'name': 'Grosse (MAP32) - Invulnerability',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 70, 'index': 70,
'doom_type': 2022, 'doom_type': 2022,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361471: {'name': 'Grosse2 (MAP32) - Super Shotgun', 361471: {'name': 'Grosse (MAP32) - Super Shotgun',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 74, 'index': 74,
'doom_type': 82, 'doom_type': 82,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361472: {'name': 'Grosse2 (MAP32) - BFG9000', 361472: {'name': 'Grosse (MAP32) - BFG9000',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 75, 'index': 75,
'doom_type': 2006, 'doom_type': 2006,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361473: {'name': 'Grosse2 (MAP32) - Megasphere', 361473: {'name': 'Grosse (MAP32) - Megasphere',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 78, 'index': 78,
'doom_type': 83, 'doom_type': 83,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361474: {'name': 'Grosse2 (MAP32) - Chaingun', 361474: {'name': 'Grosse (MAP32) - Chaingun',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 79, 'index': 79,
'doom_type': 2002, 'doom_type': 2002,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361475: {'name': 'Grosse2 (MAP32) - Chaingun 2', 361475: {'name': 'Grosse (MAP32) - Chaingun 2',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 80, 'index': 80,
'doom_type': 2002, 'doom_type': 2002,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361476: {'name': 'Grosse2 (MAP32) - Chaingun 3', 361476: {'name': 'Grosse (MAP32) - Chaingun 3',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 81, 'index': 81,
'doom_type': 2002, 'doom_type': 2002,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
361477: {'name': 'Grosse2 (MAP32) - Berserk', 361477: {'name': 'Grosse (MAP32) - Berserk',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': 82, 'index': 82,
'doom_type': 2023, 'doom_type': 2023,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Start"},
361478: {'name': 'Grosse2 (MAP32) - Exit', 361478: {'name': 'Grosse (MAP32) - Exit',
'episode': 4, 'episode': 4,
'map': 2, 'map': 2,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "Grosse2 (MAP32) Main"}, 'region': "Grosse (MAP32) Main"},
} }
location_name_groups: Dict[str, Set[str]] = { location_name_groups: Dict[str, Set[str]] = {
'Barrels o Fun (MAP23)': { "Barrels o' Fun (MAP23)": {
'Barrels o Fun (MAP23) - Armor', "Barrels o' Fun (MAP23) - Armor",
'Barrels o Fun (MAP23) - BFG9000', "Barrels o' Fun (MAP23) - BFG9000",
'Barrels o Fun (MAP23) - Backpack', "Barrels o' Fun (MAP23) - Backpack",
'Barrels o Fun (MAP23) - Backpack 2', "Barrels o' Fun (MAP23) - Backpack 2",
'Barrels o Fun (MAP23) - Berserk', "Barrels o' Fun (MAP23) - Berserk",
'Barrels o Fun (MAP23) - Computer area map', "Barrels o' Fun (MAP23) - Computer area map",
'Barrels o Fun (MAP23) - Exit', "Barrels o' Fun (MAP23) - Exit",
'Barrels o Fun (MAP23) - Megasphere', "Barrels o' Fun (MAP23) - Megasphere",
'Barrels o Fun (MAP23) - Rocket launcher', "Barrels o' Fun (MAP23) - Rocket launcher",
'Barrels o Fun (MAP23) - Shotgun', "Barrels o' Fun (MAP23) - Shotgun",
'Barrels o Fun (MAP23) - Supercharge', "Barrels o' Fun (MAP23) - Supercharge",
'Barrels o Fun (MAP23) - Yellow skull key', "Barrels o' Fun (MAP23) - Yellow skull key",
}, },
'Bloodfalls (MAP25)': { 'Bloodfalls (MAP25)': {
'Bloodfalls (MAP25) - Armor', 'Bloodfalls (MAP25) - Armor',
@@ -2998,18 +2998,18 @@ location_name_groups: Dict[str, Set[str]] = {
'Gotcha! (MAP20) - Supercharge 3', 'Gotcha! (MAP20) - Supercharge 3',
'Gotcha! (MAP20) - Supercharge 4', 'Gotcha! (MAP20) - Supercharge 4',
}, },
'Grosse2 (MAP32)': { 'Grosse (MAP32)': {
'Grosse2 (MAP32) - BFG9000', 'Grosse (MAP32) - BFG9000',
'Grosse2 (MAP32) - Berserk', 'Grosse (MAP32) - Berserk',
'Grosse2 (MAP32) - Chaingun', 'Grosse (MAP32) - Chaingun',
'Grosse2 (MAP32) - Chaingun 2', 'Grosse (MAP32) - Chaingun 2',
'Grosse2 (MAP32) - Chaingun 3', 'Grosse (MAP32) - Chaingun 3',
'Grosse2 (MAP32) - Exit', 'Grosse (MAP32) - Exit',
'Grosse2 (MAP32) - Invulnerability', 'Grosse (MAP32) - Invulnerability',
'Grosse2 (MAP32) - Megasphere', 'Grosse (MAP32) - Megasphere',
'Grosse2 (MAP32) - Plasma gun', 'Grosse (MAP32) - Plasma gun',
'Grosse2 (MAP32) - Rocket launcher', 'Grosse (MAP32) - Rocket launcher',
'Grosse2 (MAP32) - Super Shotgun', 'Grosse (MAP32) - Super Shotgun',
}, },
'Icon of Sin (MAP30)': { 'Icon of Sin (MAP30)': {
'Icon of Sin (MAP30) - BFG9000', 'Icon of Sin (MAP30) - BFG9000',
@@ -3417,22 +3417,22 @@ location_name_groups: Dict[str, Set[str]] = {
'Underhalls (MAP02) - Red keycard', 'Underhalls (MAP02) - Red keycard',
'Underhalls (MAP02) - Super Shotgun', 'Underhalls (MAP02) - Super Shotgun',
}, },
'Wolfenstein2 (MAP31)': { 'Wolfenstein (MAP31)': {
'Wolfenstein2 (MAP31) - BFG9000', 'Wolfenstein (MAP31) - BFG9000',
'Wolfenstein2 (MAP31) - Backpack', 'Wolfenstein (MAP31) - Backpack',
'Wolfenstein2 (MAP31) - Backpack 2', 'Wolfenstein (MAP31) - Backpack 2',
'Wolfenstein2 (MAP31) - Backpack 3', 'Wolfenstein (MAP31) - Backpack 3',
'Wolfenstein2 (MAP31) - Backpack 4', 'Wolfenstein (MAP31) - Backpack 4',
'Wolfenstein2 (MAP31) - Berserk', 'Wolfenstein (MAP31) - Berserk',
'Wolfenstein2 (MAP31) - Chaingun', 'Wolfenstein (MAP31) - Chaingun',
'Wolfenstein2 (MAP31) - Exit', 'Wolfenstein (MAP31) - Exit',
'Wolfenstein2 (MAP31) - Megasphere', 'Wolfenstein (MAP31) - Megasphere',
'Wolfenstein2 (MAP31) - Partial invisibility', 'Wolfenstein (MAP31) - Partial invisibility',
'Wolfenstein2 (MAP31) - Plasma gun', 'Wolfenstein (MAP31) - Plasma gun',
'Wolfenstein2 (MAP31) - Rocket launcher', 'Wolfenstein (MAP31) - Rocket launcher',
'Wolfenstein2 (MAP31) - Shotgun', 'Wolfenstein (MAP31) - Shotgun',
'Wolfenstein2 (MAP31) - Super Shotgun', 'Wolfenstein (MAP31) - Super Shotgun',
'Wolfenstein2 (MAP31) - Supercharge', 'Wolfenstein (MAP31) - Supercharge',
}, },
} }

View File

@@ -26,7 +26,7 @@ map_names: List[str] = [
'Gotcha! (MAP20)', 'Gotcha! (MAP20)',
'Nirvana (MAP21)', 'Nirvana (MAP21)',
'The Catacombs (MAP22)', 'The Catacombs (MAP22)',
'Barrels o Fun (MAP23)', "Barrels o' Fun (MAP23)",
'The Chasm (MAP24)', 'The Chasm (MAP24)',
'Bloodfalls (MAP25)', 'Bloodfalls (MAP25)',
'The Abandoned Mines (MAP26)', 'The Abandoned Mines (MAP26)',
@@ -34,6 +34,6 @@ map_names: List[str] = [
'The Spirit World (MAP28)', 'The Spirit World (MAP28)',
'The Living End (MAP29)', 'The Living End (MAP29)',
'Icon of Sin (MAP30)', 'Icon of Sin (MAP30)',
'Wolfenstein2 (MAP31)', 'Wolfenstein (MAP31)',
'Grosse2 (MAP32)', 'Grosse (MAP32)',
] ]

View File

@@ -84,11 +84,12 @@ regions:List[RegionDict] = [
# The Waste Tunnels (MAP05) # The Waste Tunnels (MAP05)
{"name":"The Waste Tunnels (MAP05) Main", {"name":"The Waste Tunnels (MAP05) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[ "connections":[
{"target":"The Waste Tunnels (MAP05) Red","pro":False}, {"target":"The Waste Tunnels (MAP05) Red","pro":False},
{"target":"The Waste Tunnels (MAP05) Blue","pro":False}]}, {"target":"The Waste Tunnels (MAP05) Blue","pro":False},
{"target":"The Waste Tunnels (MAP05) Start","pro":False}]},
{"name":"The Waste Tunnels (MAP05) Blue", {"name":"The Waste Tunnels (MAP05) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
@@ -103,6 +104,10 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"The Waste Tunnels (MAP05) Main","pro":False}]}, "connections":[{"target":"The Waste Tunnels (MAP05) Main","pro":False}]},
{"name":"The Waste Tunnels (MAP05) Start",
"connects_to_hub":True,
"episode":1,
"connections":[{"target":"The Waste Tunnels (MAP05) Main","pro":False}]},
# The Crusher (MAP06) # The Crusher (MAP06)
{"name":"The Crusher (MAP06) Main", {"name":"The Crusher (MAP06) Main",
@@ -129,9 +134,13 @@ regions:List[RegionDict] = [
# Dead Simple (MAP07) # Dead Simple (MAP07)
{"name":"Dead Simple (MAP07) Main", {"name":"Dead Simple (MAP07) Main",
"connects_to_hub":False,
"episode":1,
"connections":[{"target":"Dead Simple (MAP07) Start","pro":False}]},
{"name":"Dead Simple (MAP07) Start",
"connects_to_hub":True, "connects_to_hub":True,
"episode":1, "episode":1,
"connections":[]}, "connections":[{"target":"Dead Simple (MAP07) Main","pro":False}]},
# Tricks and Traps (MAP08) # Tricks and Traps (MAP08)
{"name":"Tricks and Traps (MAP08) Main", {"name":"Tricks and Traps (MAP08) Main",
@@ -151,11 +160,12 @@ regions:List[RegionDict] = [
# The Pit (MAP09) # The Pit (MAP09)
{"name":"The Pit (MAP09) Main", {"name":"The Pit (MAP09) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[ "connections":[
{"target":"The Pit (MAP09) Yellow","pro":False}, {"target":"The Pit (MAP09) Yellow","pro":False},
{"target":"The Pit (MAP09) Blue","pro":False}]}, {"target":"The Pit (MAP09) Blue","pro":False},
{"target":"The Pit (MAP09) Start","pro":False}]},
{"name":"The Pit (MAP09) Blue", {"name":"The Pit (MAP09) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
@@ -164,12 +174,18 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"The Pit (MAP09) Main","pro":False}]}, "connections":[{"target":"The Pit (MAP09) Main","pro":False}]},
{"name":"The Pit (MAP09) Start",
"connects_to_hub":True,
"episode":1,
"connections":[{"target":"The Pit (MAP09) Main","pro":False}]},
# Refueling Base (MAP10) # Refueling Base (MAP10)
{"name":"Refueling Base (MAP10) Main", {"name":"Refueling Base (MAP10) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Refueling Base (MAP10) Yellow","pro":False}]}, "connections":[
{"target":"Refueling Base (MAP10) Yellow","pro":False},
{"target":"Refueling Base (MAP10) Start","pro":False}]},
{"name":"Refueling Base (MAP10) Yellow", {"name":"Refueling Base (MAP10) Yellow",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
@@ -180,6 +196,10 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Refueling Base (MAP10) Yellow","pro":False}]}, "connections":[{"target":"Refueling Base (MAP10) Yellow","pro":False}]},
{"name":"Refueling Base (MAP10) Start",
"connects_to_hub":True,
"episode":1,
"connections":[{"target":"Refueling Base (MAP10) Main","pro":False}]},
# Circle of Death (MAP11) # Circle of Death (MAP11)
{"name":"Circle of Death (MAP11) Main", {"name":"Circle of Death (MAP11) Main",
@@ -187,31 +207,49 @@ regions:List[RegionDict] = [
"episode":1, "episode":1,
"connections":[ "connections":[
{"target":"Circle of Death (MAP11) Blue","pro":False}, {"target":"Circle of Death (MAP11) Blue","pro":False},
{"target":"Circle of Death (MAP11) Red","pro":False}]}, {"target":"Circle of Death (MAP11) Red","pro":False},
{"target":"Circle of Death (MAP11) Ending","pro":True}]},
{"name":"Circle of Death (MAP11) Blue", {"name":"Circle of Death (MAP11) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Circle of Death (MAP11) Main","pro":False}]}, "connections":[{"target":"Circle of Death (MAP11) Main","pro":False}]},
{"name":"Circle of Death (MAP11) Red", {"name":"Circle of Death (MAP11) Red",
"connects_to_hub":False,
"episode":1,
"connections":[
{"target":"Circle of Death (MAP11) Main","pro":False},
{"target":"Circle of Death (MAP11) Ending","pro":False}]},
{"name":"Circle of Death (MAP11) Ending",
"connects_to_hub":False, "connects_to_hub":False,
"episode":1, "episode":1,
"connections":[{"target":"Circle of Death (MAP11) Main","pro":False}]}, "connections":[{"target":"Circle of Death (MAP11) Main","pro":False}]},
# The Factory (MAP12) # The Factory (MAP12)
{"name":"The Factory (MAP12) Main", {"name":"The Factory (MAP12) Indoors",
"connects_to_hub":True, "connects_to_hub":False,
"episode":2, "episode":2,
"connections":[ "connections":[
{"target":"The Factory (MAP12) Yellow","pro":False}, {"target":"The Factory (MAP12) Yellow","pro":False},
{"target":"The Factory (MAP12) Blue","pro":False}]}, {"target":"The Factory (MAP12) Blue","pro":False},
{"target":"The Factory (MAP12) Main","pro":False}]},
{"name":"The Factory (MAP12) Blue", {"name":"The Factory (MAP12) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":2, "episode":2,
"connections":[{"target":"The Factory (MAP12) Main","pro":False}]}, "connections":[{"target":"The Factory (MAP12) Indoors","pro":False}]},
{"name":"The Factory (MAP12) Yellow", {"name":"The Factory (MAP12) Yellow",
"connects_to_hub":False, "connects_to_hub":False,
"episode":2, "episode":2,
"connections":[]}, "connections":[]},
{"name":"The Factory (MAP12) Outdoors",
"connects_to_hub":True,
"episode":2,
"connections":[{"target":"The Factory (MAP12) Main","pro":False}]},
{"name":"The Factory (MAP12) Main",
"connects_to_hub":False,
"episode":2,
"connections":[
{"target":"The Factory (MAP12) Indoors","pro":False},
{"target":"The Factory (MAP12) Outdoors","pro":False}]},
# Downtown (MAP13) # Downtown (MAP13)
{"name":"Downtown (MAP13) Main", {"name":"Downtown (MAP13) Main",
@@ -291,7 +329,8 @@ regions:List[RegionDict] = [
"episode":2, "episode":2,
"connections":[ "connections":[
{"target":"Suburbs (MAP16) Red","pro":False}, {"target":"Suburbs (MAP16) Red","pro":False},
{"target":"Suburbs (MAP16) Blue","pro":False}]}, {"target":"Suburbs (MAP16) Blue","pro":False},
{"target":"Suburbs (MAP16) Pro Exit","pro":True}]},
{"name":"Suburbs (MAP16) Blue", {"name":"Suburbs (MAP16) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":2, "episode":2,
@@ -299,7 +338,13 @@ regions:List[RegionDict] = [
{"name":"Suburbs (MAP16) Red", {"name":"Suburbs (MAP16) Red",
"connects_to_hub":False, "connects_to_hub":False,
"episode":2, "episode":2,
"connections":[{"target":"Suburbs (MAP16) Main","pro":False}]}, "connections":[
{"target":"Suburbs (MAP16) Main","pro":False},
{"target":"Suburbs (MAP16) Pro Exit","pro":False}]},
{"name":"Suburbs (MAP16) Pro Exit",
"connects_to_hub":False,
"episode":2,
"connections":[{"target":"Suburbs (MAP16) Red","pro":False}]},
# Tenements (MAP17) # Tenements (MAP17)
{"name":"Tenements (MAP17) Main", {"name":"Tenements (MAP17) Main",
@@ -358,7 +403,7 @@ regions:List[RegionDict] = [
# Nirvana (MAP21) # Nirvana (MAP21)
{"name":"Nirvana (MAP21) Main", {"name":"Nirvana (MAP21) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"Nirvana (MAP21) Yellow","pro":False}]}, "connections":[{"target":"Nirvana (MAP21) Yellow","pro":False}]},
{"name":"Nirvana (MAP21) Yellow", {"name":"Nirvana (MAP21) Yellow",
@@ -366,19 +411,31 @@ regions:List[RegionDict] = [
"episode":3, "episode":3,
"connections":[ "connections":[
{"target":"Nirvana (MAP21) Main","pro":False}, {"target":"Nirvana (MAP21) Main","pro":False},
{"target":"Nirvana (MAP21) Magenta","pro":False}]}, {"target":"Nirvana (MAP21) Magenta","pro":False},
{"target":"Nirvana (MAP21) Pro Magenta","pro":True}]},
{"name":"Nirvana (MAP21) Magenta", {"name":"Nirvana (MAP21) Magenta",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"Nirvana (MAP21) Yellow","pro":False}]}, "connections":[
{"target":"Nirvana (MAP21) Yellow","pro":False},
{"target":"Nirvana (MAP21) Pro Magenta","pro":False}]},
{"name":"Nirvana (MAP21) Start",
"connects_to_hub":True,
"episode":3,
"connections":[{"target":"Nirvana (MAP21) Main","pro":False}]},
{"name":"Nirvana (MAP21) Pro Magenta",
"connects_to_hub":False,
"episode":3,
"connections":[{"target":"Nirvana (MAP21) Magenta","pro":False}]},
# The Catacombs (MAP22) # The Catacombs (MAP22)
{"name":"The Catacombs (MAP22) Main", {"name":"The Catacombs (MAP22) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[ "connections":[
{"target":"The Catacombs (MAP22) Blue","pro":False}, {"target":"The Catacombs (MAP22) Blue","pro":False},
{"target":"The Catacombs (MAP22) Red","pro":False}]}, {"target":"The Catacombs (MAP22) Red","pro":False},
{"target":"The Catacombs (MAP22) Early","pro":False}]},
{"name":"The Catacombs (MAP22) Blue", {"name":"The Catacombs (MAP22) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
@@ -387,36 +444,59 @@ regions:List[RegionDict] = [
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"The Catacombs (MAP22) Main","pro":False}]}, "connections":[{"target":"The Catacombs (MAP22) Main","pro":False}]},
{"name":"The Catacombs (MAP22) Early",
# Barrels o Fun (MAP23)
{"name":"Barrels o Fun (MAP23) Main",
"connects_to_hub":True, "connects_to_hub":True,
"episode":3, "episode":3,
"connections":[{"target":"Barrels o Fun (MAP23) Yellow","pro":False}]}, "connections":[{"target":"The Catacombs (MAP22) Main","pro":False}]},
{"name":"Barrels o Fun (MAP23) Yellow",
# Barrels o' Fun (MAP23)
{"name":"Barrels o' Fun (MAP23) Main",
"connects_to_hub":True,
"episode":3,
"connections":[{"target":"Barrels o' Fun (MAP23) Yellow","pro":False}]},
{"name":"Barrels o' Fun (MAP23) Yellow",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"Barrels o Fun (MAP23) Main","pro":False}]}, "connections":[{"target":"Barrels o' Fun (MAP23) Main","pro":False}]},
# The Chasm (MAP24) # The Chasm (MAP24)
{"name":"The Chasm (MAP24) Main", {"name":"The Chasm (MAP24) Main",
"connects_to_hub":True, "connects_to_hub":True,
"episode":3, "episode":3,
"connections":[{"target":"The Chasm (MAP24) Red","pro":False}]}, "connections":[
{"target":"The Chasm (MAP24) Blue","pro":False},
{"target":"The Chasm (MAP24) Blue Pro","pro":True}]},
{"name":"The Chasm (MAP24) Red", {"name":"The Chasm (MAP24) Red",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"The Chasm (MAP24) Main","pro":False}]}, "connections":[{"target":"The Chasm (MAP24) Blue","pro":False}]},
{"name":"The Chasm (MAP24) Blue",
"connects_to_hub":False,
"episode":3,
"connections":[
{"target":"The Chasm (MAP24) Red","pro":False},
{"target":"The Chasm (MAP24) Main","pro":False},
{"target":"The Chasm (MAP24) Blue Pro","pro":False}]},
{"name":"The Chasm (MAP24) Blue Pro",
"connects_to_hub":False,
"episode":3,
"connections":[{"target":"The Chasm (MAP24) Blue","pro":False}]},
# Bloodfalls (MAP25) # Bloodfalls (MAP25)
{"name":"Bloodfalls (MAP25) Main", {"name":"Bloodfalls (MAP25) Main",
"connects_to_hub":True, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"Bloodfalls (MAP25) Blue","pro":False}]}, "connections":[
{"target":"Bloodfalls (MAP25) Blue","pro":False},
{"target":"Bloodfalls (MAP25) Start","pro":False}]},
{"name":"Bloodfalls (MAP25) Blue", {"name":"Bloodfalls (MAP25) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[{"target":"Bloodfalls (MAP25) Main","pro":False}]}, "connections":[{"target":"Bloodfalls (MAP25) Main","pro":False}]},
{"name":"Bloodfalls (MAP25) Start",
"connects_to_hub":True,
"episode":3,
"connections":[{"target":"Bloodfalls (MAP25) Main","pro":False}]},
# The Abandoned Mines (MAP26) # The Abandoned Mines (MAP26)
{"name":"The Abandoned Mines (MAP26) Main", {"name":"The Abandoned Mines (MAP26) Main",
@@ -484,19 +564,27 @@ regions:List[RegionDict] = [
# Icon of Sin (MAP30) # Icon of Sin (MAP30)
{"name":"Icon of Sin (MAP30) Main", {"name":"Icon of Sin (MAP30) Main",
"connects_to_hub":False,
"episode":3,
"connections":[{"target":"Icon of Sin (MAP30) Start","pro":False}]},
{"name":"Icon of Sin (MAP30) Start",
"connects_to_hub":True, "connects_to_hub":True,
"episode":3, "episode":3,
"connections":[]}, "connections":[{"target":"Icon of Sin (MAP30) Main","pro":False}]},
# Wolfenstein2 (MAP31) # Wolfenstein (MAP31)
{"name":"Wolfenstein2 (MAP31) Main", {"name":"Wolfenstein (MAP31) Main",
"connects_to_hub":True, "connects_to_hub":True,
"episode":4, "episode":4,
"connections":[]}, "connections":[]},
# Grosse2 (MAP32) # Grosse (MAP32)
{"name":"Grosse2 (MAP32) Main", {"name":"Grosse (MAP32) Main",
"connects_to_hub":False,
"episode":4,
"connections":[{"target":"Grosse (MAP32) Start","pro":False}]},
{"name":"Grosse (MAP32) Start",
"connects_to_hub":True, "connects_to_hub":True,
"episode":4, "episode":4,
"connections":[]}, "connections":[{"target":"Grosse (MAP32) Main","pro":False}]},
] ]

View File

@@ -53,14 +53,6 @@ def set_episode1_rules(player, multiworld, pro):
state.has("The Focus (MAP04) - Red keycard", player, 1)) state.has("The Focus (MAP04) - Red keycard", player, 1))
# The Waste Tunnels (MAP05) # The Waste Tunnels (MAP05)
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
state.has("Super 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(multiworld.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)) state.has("The Waste Tunnels (MAP05) - Red keycard", player, 1))
set_rule(multiworld.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:
@@ -71,18 +63,22 @@ def set_episode1_rules(player, multiworld, pro):
state.has("The Waste Tunnels (MAP05) - Blue keycard", player, 1)) state.has("The Waste Tunnels (MAP05) - Blue keycard", player, 1))
set_rule(multiworld.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)) state.has("The Waste Tunnels (MAP05) - Yellow keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> The Waste Tunnels (MAP05) Start", player), lambda state:
state.has("The Waste Tunnels (MAP05)", player, 1))
set_rule(multiworld.get_entrance("The Waste Tunnels (MAP05) Start -> The Waste Tunnels (MAP05) Main", player), lambda state:
(state.has("Shotgun", player, 1) and
state.has("Super Shotgun", player, 1)) and (state.has("Chaingun", player, 1) or
state.has("Plasma gun", player, 1)))
# The Crusher (MAP06) # The Crusher (MAP06)
set_rule(multiworld.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("The Crusher (MAP06)", player, 1) and
state.has("Shotgun", player, 1) and state.has("Shotgun", player, 1)) and
state.has("Chaingun", player, 1) and (state.has("Plasma gun", player, 1) or
state.has("Super Shotgun", player, 1)) and state.has("Chaingun", player, 1)))
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(multiworld.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)) state.has("The Crusher (MAP06) - Blue keycard", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(multiworld.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)) state.has("The Crusher (MAP06) - Red keycard", player, 1))
set_rule(multiworld.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:
@@ -95,14 +91,14 @@ def set_episode1_rules(player, multiworld, pro):
state.has("The Crusher (MAP06) - Red keycard", player, 1)) state.has("The Crusher (MAP06) - Red keycard", player, 1))
# Dead Simple (MAP07) # Dead Simple (MAP07)
set_rule(multiworld.get_entrance("Hub -> Dead Simple (MAP07) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Dead Simple (MAP07) Start", player), lambda state:
(state.has("Dead Simple (MAP07)", player, 1) and state.has("Dead Simple (MAP07)", player, 1))
state.has("Shotgun", player, 1) and set_rule(multiworld.get_entrance("Dead Simple (MAP07) Start -> Dead Simple (MAP07) Main", player), lambda state:
(state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and state.has("Chaingun", player, 1) and
state.has("Super Shotgun", player, 1)) and state.has("Super Shotgun", player, 1)) and (state.has("BFG9000", player, 1) or
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1))) state.has("Rocket launcher", player, 1)))
# Tricks and Traps (MAP08) # Tricks and Traps (MAP08)
set_rule(multiworld.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:
@@ -119,34 +115,34 @@ def set_episode1_rules(player, multiworld, pro):
state.has("Tricks and Traps (MAP08) - Yellow skull key", player, 1)) state.has("Tricks and Traps (MAP08) - Yellow skull key", player, 1))
# The Pit (MAP09) # The Pit (MAP09)
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
state.has("Super 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(multiworld.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)) state.has("The Pit (MAP09) - Yellow keycard", player, 1))
set_rule(multiworld.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)) state.has("The Pit (MAP09) - Blue keycard", player, 1))
set_rule(multiworld.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)) state.has("The Pit (MAP09) - Yellow keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> The Pit (MAP09) Start", player), lambda state:
state.has("The Pit (MAP09)", player, 1))
set_rule(multiworld.get_entrance("The Pit (MAP09) Start -> The Pit (MAP09) Main", player), lambda state:
(state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Super Shotgun", player, 1)) and (state.has("BFG9000", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("Rocket launcher", player, 1)))
# Refueling Base (MAP10) # Refueling Base (MAP10)
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
state.has("Super 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(multiworld.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)) state.has("Refueling Base (MAP10) - Yellow keycard", player, 1))
set_rule(multiworld.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)) state.has("Refueling Base (MAP10) - Blue keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> Refueling Base (MAP10) Start", player), lambda state:
state.has("Refueling Base (MAP10)", player, 1))
set_rule(multiworld.get_entrance("Refueling Base (MAP10) Start -> Refueling Base (MAP10) Main", player), lambda state:
(state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Super Shotgun", player, 1)) and (state.has("BFG9000", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("Rocket launcher", player, 1)))
# Circle of Death (MAP11) # Circle of Death (MAP11)
set_rule(multiworld.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:
@@ -165,18 +161,19 @@ def set_episode1_rules(player, multiworld, pro):
def set_episode2_rules(player, multiworld, pro): def set_episode2_rules(player, multiworld, pro):
# The Factory (MAP12) # The Factory (MAP12)
set_rule(multiworld.get_entrance("Hub -> The Factory (MAP12) Main", player), lambda state: set_rule(multiworld.get_entrance("The Factory (MAP12) Indoors -> The Factory (MAP12) Yellow", player), lambda state:
(state.has("The Factory (MAP12)", player, 1) and
state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Super 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(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Yellow", player), lambda state:
state.has("The Factory (MAP12) - Yellow keycard", player, 1)) state.has("The Factory (MAP12) - Yellow keycard", player, 1))
set_rule(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Blue", player), lambda state: set_rule(multiworld.get_entrance("The Factory (MAP12) Indoors -> The Factory (MAP12) Blue", player), lambda state:
state.has("The Factory (MAP12) - Blue keycard", player, 1)) state.has("The Factory (MAP12) - Blue keycard", player, 1))
set_rule(multiworld.get_entrance("Hub -> The Factory (MAP12) Outdoors", player), lambda state:
state.has("The Factory (MAP12)", player, 1))
set_rule(multiworld.get_entrance("The Factory (MAP12) Outdoors -> The Factory (MAP12) Main", player), lambda state:
state.has("Super Shotgun", player, 1) or
state.has("Plasma gun", player, 1))
set_rule(multiworld.get_entrance("The Factory (MAP12) Main -> The Factory (MAP12) Indoors", player), lambda state:
(state.has("Super Shotgun", player, 1) and
state.has("Chaingun", player, 1)) and (state.has("BFG9000", player, 1) or
state.has("Plasma gun", player, 1)))
# Downtown (MAP13) # Downtown (MAP13)
set_rule(multiworld.get_entrance("Hub -> Downtown (MAP13) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Downtown (MAP13) Main", player), lambda state:
@@ -307,54 +304,56 @@ def set_episode2_rules(player, multiworld, pro):
def set_episode3_rules(player, multiworld, pro): def set_episode3_rules(player, multiworld, pro):
# Nirvana (MAP21) # Nirvana (MAP21)
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
state.has("Super 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(multiworld.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)) (state.has("Super Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Nirvana (MAP21) - Yellow skull key", player, 1)) and (state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(multiworld.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)) state.has("Nirvana (MAP21) - Yellow skull key", player, 1))
set_rule(multiworld.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) - Red skull key", player, 1) and
state.has("Nirvana (MAP21) - Blue skull key", player, 1)) state.has("Nirvana (MAP21) - Blue skull key", player, 1))
set_rule(multiworld.get_entrance("Nirvana (MAP21) Magenta -> Nirvana (MAP21) Yellow", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Nirvana (MAP21) Start", player), lambda state:
state.has("Nirvana (MAP21) - Red skull key", player, 1) and state.has("Nirvana (MAP21)", player, 1))
state.has("Nirvana (MAP21) - Blue skull key", player, 1)) set_rule(multiworld.get_entrance("Nirvana (MAP21) Start -> Nirvana (MAP21) Main", player), lambda state:
state.has("Super Shotgun", player, 1) or
state.has("Plasma gun", player, 1))
set_rule(multiworld.get_entrance("Nirvana (MAP21) Pro Magenta -> Nirvana (MAP21) Magenta", player), lambda state:
state.has("Nirvana (MAP21) - Red skull key", player, 1))
# The Catacombs (MAP22) # The Catacombs (MAP22)
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
state.has("Super Shotgun", player, 1)) and
(state.has("BFG9000", player, 1) or
state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1)))
set_rule(multiworld.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)) state.has("The Catacombs (MAP22) - Blue skull key", player, 1))
set_rule(multiworld.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)) state.has("The Catacombs (MAP22) - Red skull key", player, 1))
set_rule(multiworld.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)) state.has("The Catacombs (MAP22) - Red skull key", player, 1))
set_rule(multiworld.get_entrance("Hub -> The Catacombs (MAP22) Early", player), lambda state:
(state.has("The Catacombs (MAP22)", player, 1)) and
(state.has("Shotgun", player, 1) or
state.has("Super Shotgun", player, 1) or
state.has("Plasma gun", player, 1)))
set_rule(multiworld.get_entrance("The Catacombs (MAP22) Early -> The Catacombs (MAP22) Main", player), lambda state:
(state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Super Shotgun", player, 1)) and (state.has("BFG9000", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("Rocket launcher", player, 1)))
# Barrels o Fun (MAP23) # Barrels o' Fun (MAP23)
set_rule(multiworld.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("Barrels o' Fun (MAP23)", player, 1) and
state.has("Shotgun", player, 1) and state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and state.has("Chaingun", player, 1) and
state.has("Super Shotgun", player, 1)) and state.has("Super Shotgun", player, 1)) and
(state.has("Rocket launcher", player, 1) or (state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1))) state.has("BFG9000", player, 1)))
set_rule(multiworld.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)) state.has("Barrels o' Fun (MAP23) - Yellow skull key", player, 1))
set_rule(multiworld.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)) state.has("Barrels o' Fun (MAP23) - Yellow skull key", player, 1))
# The Chasm (MAP24) # The Chasm (MAP24)
set_rule(multiworld.get_entrance("Hub -> The Chasm (MAP24) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> The Chasm (MAP24) Main", player), lambda state:
@@ -365,24 +364,26 @@ def set_episode3_rules(player, multiworld, pro):
state.has("Plasma gun", player, 1) and state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1) and state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1)) state.has("Super Shotgun", player, 1))
set_rule(multiworld.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) Blue", player), lambda state:
state.has("The Chasm (MAP24) - Blue keycard", player, 1))
set_rule(multiworld.get_entrance("The Chasm (MAP24) Red -> The Chasm (MAP24) Blue", player), lambda state:
state.has("The Chasm (MAP24) - Red keycard", player, 1)) state.has("The Chasm (MAP24) - Red keycard", player, 1))
set_rule(multiworld.get_entrance("The Chasm (MAP24) Red -> The Chasm (MAP24) Main", player), lambda state: set_rule(multiworld.get_entrance("The Chasm (MAP24) Blue -> The Chasm (MAP24) Red", player), lambda state:
state.has("The Chasm (MAP24) - Red keycard", player, 1)) state.has("The Chasm (MAP24) - Red keycard", player, 1))
# Bloodfalls (MAP25) # Bloodfalls (MAP25)
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
state.has("Rocket launcher", player, 1) and
state.has("Plasma gun", player, 1) and
state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1))
set_rule(multiworld.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)) (state.has("Bloodfalls (MAP25) - Blue skull key", player, 1)) and (state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
set_rule(multiworld.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)) state.has("Bloodfalls (MAP25) - Blue skull key", player, 1))
set_rule(multiworld.get_entrance("Hub -> Bloodfalls (MAP25) Start", player), lambda state:
state.has("Bloodfalls (MAP25)", player, 1))
set_rule(multiworld.get_entrance("Bloodfalls (MAP25) Start -> Bloodfalls (MAP25) Main", player), lambda state:
state.has("Super Shotgun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("Shotgun", player, 1))
# The Abandoned Mines (MAP26) # The Abandoned Mines (MAP26)
set_rule(multiworld.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:
@@ -451,36 +452,34 @@ def set_episode3_rules(player, multiworld, pro):
state.has("Super Shotgun", player, 1)) state.has("Super Shotgun", player, 1))
# Icon of Sin (MAP30) # Icon of Sin (MAP30)
set_rule(multiworld.get_entrance("Hub -> Icon of Sin (MAP30) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Icon of Sin (MAP30) Start", player), lambda state:
state.has("Icon of Sin (MAP30)", player, 1) and state.has("Icon of Sin (MAP30)", player, 1))
state.has("Rocket launcher", player, 1) and set_rule(multiworld.get_entrance("Icon of Sin (MAP30) Start -> Icon of Sin (MAP30) Main", player), lambda state:
state.has("Shotgun", 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("Plasma gun", player, 1) and
state.has("Chaingun", player, 1) and
state.has("BFG9000", player, 1) and state.has("BFG9000", player, 1) and
state.has("Super Shotgun", player, 1)) state.has("Super Shotgun", player, 1))
def set_episode4_rules(player, multiworld, pro): def set_episode4_rules(player, multiworld, pro):
# Wolfenstein2 (MAP31) # Wolfenstein (MAP31)
set_rule(multiworld.get_entrance("Hub -> Wolfenstein2 (MAP31) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Wolfenstein (MAP31) Main", player), lambda state:
(state.has("Wolfenstein2 (MAP31)", player, 1) and (state.has("Wolfenstein (MAP31)", player, 1) and
state.has("Shotgun", player, 1) and state.has("Chaingun", player, 1)) and
state.has("Chaingun", player, 1) and (state.has("Shotgun", player, 1) or
state.has("Super Shotgun", player, 1)) and state.has("Super Shotgun", player, 1)))
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1)))
# Grosse2 (MAP32) # Grosse (MAP32)
set_rule(multiworld.get_entrance("Hub -> Grosse2 (MAP32) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> Grosse (MAP32) Start", player), lambda state:
(state.has("Grosse2 (MAP32)", player, 1) and state.has("Grosse (MAP32)", player, 1))
state.has("Shotgun", player, 1) and set_rule(multiworld.get_entrance("Grosse (MAP32) Start -> Grosse (MAP32) Main", player), lambda state:
(state.has("Shotgun", player, 1) and
state.has("Chaingun", player, 1) and state.has("Chaingun", player, 1) and
state.has("Super Shotgun", player, 1)) and state.has("Super Shotgun", player, 1)) and (state.has("BFG9000", player, 1) or
(state.has("Rocket launcher", player, 1) or
state.has("Plasma gun", player, 1) or state.has("Plasma gun", player, 1) or
state.has("BFG9000", player, 1))) state.has("Rocket launcher", player, 1)))
def set_rules(doom_ii_world: "DOOM2World", included_episodes, pro): def set_rules(doom_ii_world: "DOOM2World", included_episodes, pro):

View File

@@ -51,11 +51,11 @@ class DOOM2World(World):
location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()} location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
location_name_groups = Locations.location_name_groups location_name_groups = Locations.location_name_groups
starting_level_for_episode: List[str] = [ starting_level_for_episode: Dict[int, str] = {
"Entryway (MAP01)", 1: "Entryway (MAP01)",
"The Factory (MAP12)", 2: "The Factory (MAP12)",
"Nirvana (MAP21)" 3: "Nirvana (MAP21)"
] }
# Item ratio that scales depending on episode count. These are the ratio for 3 episode. In DOOM1. # Item ratio that scales depending on episode count. These are the ratio for 3 episode. In DOOM1.
# The ratio have been tweaked seem, and feel good. # The ratio have been tweaked seem, and feel good.
@@ -77,6 +77,7 @@ class DOOM2World(World):
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, multiworld: MultiWorld, player: int):
self.included_episodes = [1, 1, 1, 0] self.included_episodes = [1, 1, 1, 0]
self.location_count = 0 self.location_count = 0
self.starting_levels = []
super().__init__(multiworld, player) super().__init__(multiworld, player)
@@ -95,6 +96,14 @@ class DOOM2World(World):
if self.get_episode_count() == 0: if self.get_episode_count() == 0:
self.included_episodes[0] = 1 self.included_episodes[0] = 1
self.starting_levels = [level_name for (episode, level_name) in self.starting_level_for_episode.items()
if self.included_episodes[episode - 1]]
# If soloing MAP21-MAP30, we need to mark a weapon as early to help generation succeed
if self.get_episode_count() == 1 and self.included_episodes[2]:
early_weapon = self.random.choice(["Super Shotgun", "Plasma gun"])
self.multiworld.early_items[self.player][early_weapon] = 1
def create_regions(self): def create_regions(self):
pro = self.options.pro.value pro = self.options.pro.value
@@ -193,7 +202,7 @@ class DOOM2World(World):
if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]: if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]:
continue continue
count = item["count"] if item["name"] not in self.starting_level_for_episode else item["count"] - 1 count = item["count"] if item["name"] not in self.starting_levels else item["count"] - 1
itempool += [self.create_item(item["name"]) for _ in range(count)] itempool += [self.create_item(item["name"]) for _ in range(count)]
# Backpack(s) based on options # Backpack(s) based on options
@@ -224,9 +233,8 @@ class DOOM2World(World):
self.location_count -= 1 self.location_count -= 1
# Give starting levels right away # Give starting levels right away
for i in range(len(self.starting_level_for_episode)): for map_name in self.starting_levels:
if self.included_episodes[i]: self.multiworld.push_precollected(self.create_item(map_name))
self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i]))
# Give Computer area maps if option selected # Give Computer area maps if option selected
if start_with_computer_area_maps: if start_with_computer_area_maps:

View File

@@ -255,7 +255,8 @@ async def game_watcher(ctx: FactorioContext):
if "DeathLink" in ctx.tags: if "DeathLink" in ctx.tags:
async_start(ctx.send_death()) async_start(ctx.send_death())
if ctx.energy_link_increment: if ctx.energy_link_increment:
in_world_bridges = data["energy_bridges"] # 1 + quality * 0.3 for each bridge
in_world_bridges: float = data["energy_bridges"]
if in_world_bridges: if in_world_bridges:
in_world_energy = data["energy"] in_world_energy = data["energy"]
if in_world_energy < (ctx.energy_link_increment * in_world_bridges): if in_world_energy < (ctx.energy_link_increment * in_world_bridges):
@@ -263,14 +264,14 @@ async def game_watcher(ctx: FactorioContext):
ctx.last_deplete = time.time() ctx.last_deplete = time.time()
async_start(ctx.send_msgs([{ async_start(ctx.send_msgs([{
"cmd": "Set", "key": ctx.energylink_key, "operations": "cmd": "Set", "key": ctx.energylink_key, "operations":
[{"operation": "add", "value": -ctx.energy_link_increment * in_world_bridges}, [{"operation": "add", "value": int(-ctx.energy_link_increment * in_world_bridges)},
{"operation": "max", "value": 0}], {"operation": "max", "value": 0}],
"last_deplete": ctx.last_deplete "last_deplete": ctx.last_deplete
}])) }]))
# Above Capacity - (len(Bridges) * ENERGY_INCREMENT) # Above Capacity - (len(Bridges) * ENERGY_INCREMENT)
elif in_world_energy > (in_world_bridges * ctx.energy_link_increment * 5) - \ elif in_world_energy > (in_world_bridges * ctx.energy_link_increment * 5) - \
ctx.energy_link_increment * in_world_bridges: ctx.energy_link_increment * in_world_bridges:
value = ctx.energy_link_increment * in_world_bridges value = int(ctx.energy_link_increment * in_world_bridges)
async_start(ctx.send_msgs([{ async_start(ctx.send_msgs([{
"cmd": "Set", "key": ctx.energylink_key, "operations": "cmd": "Set", "key": ctx.energylink_key, "operations":
[{"operation": "add", "value": value}] [{"operation": "add", "value": value}]
@@ -406,7 +407,7 @@ async def get_info(ctx: FactorioContext, rcon_client: factorio_rcon.RCONClient):
ctx.auth = info["slot_name"] ctx.auth = info["slot_name"]
ctx.seed_name = info["seed_name"] ctx.seed_name = info["seed_name"]
death_link = info["death_link"] death_link = info["death_link"]
ctx.energy_link_increment = info.get("energy_link", 0) ctx.energy_link_increment = int(info.get("energy_link", 0))
logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}") logger.debug(f"Energy Link Increment: {ctx.energy_link_increment}")
if ctx.energy_link_increment and ctx.ui: if ctx.energy_link_increment and ctx.ui:
ctx.ui.enable_energy_link() ctx.ui.enable_energy_link()
@@ -530,7 +531,7 @@ server_args = ("--rcon-port", rcon_port, "--rcon-password", rcon_password)
def launch(): def launch():
import colorama import colorama
global executable, server_settings, server_args global executable, server_settings, server_args
colorama.init() colorama.just_fix_windows_console()
if server_settings: if server_settings:
server_settings = os.path.abspath(server_settings) server_settings = os.path.abspath(server_settings)

View File

@@ -102,7 +102,7 @@ class Factorio(World):
item_name_groups = { item_name_groups = {
"Progressive": set(progressive_tech_table.keys()), "Progressive": set(progressive_tech_table.keys()),
} }
required_client_version = (0, 5, 1) required_client_version = (0, 6, 0)
if Utils.version_tuple < required_client_version: if Utils.version_tuple < required_client_version:
raise Exception(f"Update Archipelago to use this world ({game}).") raise Exception(f"Update Archipelago to use this world ({game}).")
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs() ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()

View File

@@ -3,7 +3,6 @@ import settings
import base64 import base64
import threading import threading
import requests import requests
import yaml
from worlds.AutoWorld import World, WebWorld from worlds.AutoWorld import World, WebWorld
from BaseClasses import Tutorial from BaseClasses import Tutorial
from .Regions import create_regions, location_table, set_rules, stage_set_rules, rooms, non_dead_end_crest_rooms,\ from .Regions import create_regions, location_table, set_rules, stage_set_rules, rooms, non_dead_end_crest_rooms,\
@@ -134,7 +133,7 @@ class FFMQWorld(World):
errors.append([api_url, err]) errors.append([api_url, err])
else: else:
if response.ok: if response.ok:
world.rooms = rooms_data[query] = yaml.load(response.text, yaml.Loader) world.rooms = rooms_data[query] = Utils.parse_yaml(response.text)
break break
else: else:
api_urls.remove(api_url) api_urls.remove(api_url)

View File

View File

@@ -514,19 +514,19 @@ item_table: Dict[int, ItemDict] = {
'map': 7}, 'map': 7},
370259: {'classification': ItemClassification.progression, 370259: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'The Aquifier (E3M9) - Blue key', 'name': 'The Aquifer (E3M9) - Blue key',
'doom_type': 79, 'doom_type': 79,
'episode': 3, 'episode': 3,
'map': 9}, 'map': 9},
370260: {'classification': ItemClassification.progression, 370260: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'The Aquifier (E3M9) - Green key', 'name': 'The Aquifer (E3M9) - Green key',
'doom_type': 73, 'doom_type': 73,
'episode': 3, 'episode': 3,
'map': 9}, 'map': 9},
370261: {'classification': ItemClassification.progression, 370261: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'The Aquifier (E3M9) - Yellow key', 'name': 'The Aquifer (E3M9) - Yellow key',
'doom_type': 80, 'doom_type': 80,
'episode': 3, 'episode': 3,
'map': 9}, 'map': 9},
@@ -1234,37 +1234,37 @@ item_table: Dict[int, ItemDict] = {
'map': 7}, 'map': 7},
370475: {'classification': ItemClassification.progression, 370475: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': "D'Sparil'S Keep (E3M8)", 'name': "D'Sparil's Keep (E3M8)",
'doom_type': -1, 'doom_type': -1,
'episode': 3, 'episode': 3,
'map': 8}, 'map': 8},
370476: {'classification': ItemClassification.progression, 370476: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': "D'Sparil'S Keep (E3M8) - Complete", 'name': "D'Sparil's Keep (E3M8) - Complete",
'doom_type': -2, 'doom_type': -2,
'episode': 3, 'episode': 3,
'map': 8}, 'map': 8},
370477: {'classification': ItemClassification.filler, 370477: {'classification': ItemClassification.filler,
'count': 1, 'count': 1,
'name': "D'Sparil'S Keep (E3M8) - Map Scroll", 'name': "D'Sparil's Keep (E3M8) - Map Scroll",
'doom_type': 35, 'doom_type': 35,
'episode': 3, 'episode': 3,
'map': 8}, 'map': 8},
370478: {'classification': ItemClassification.progression, 370478: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'The Aquifier (E3M9)', 'name': 'The Aquifer (E3M9)',
'doom_type': -1, 'doom_type': -1,
'episode': 3, 'episode': 3,
'map': 9}, 'map': 9},
370479: {'classification': ItemClassification.progression, 370479: {'classification': ItemClassification.progression,
'count': 1, 'count': 1,
'name': 'The Aquifier (E3M9) - Complete', 'name': 'The Aquifer (E3M9) - Complete',
'doom_type': -2, 'doom_type': -2,
'episode': 3, 'episode': 3,
'map': 9}, 'map': 9},
370480: {'classification': ItemClassification.filler, 370480: {'classification': ItemClassification.filler,
'count': 1, 'count': 1,
'name': 'The Aquifier (E3M9) - Map Scroll', 'name': 'The Aquifer (E3M9) - Map Scroll',
'doom_type': 35, 'doom_type': 35,
'episode': 3, 'episode': 3,
'map': 9}, 'map': 9},
@@ -1635,8 +1635,8 @@ item_name_groups: Dict[str, Set[str]] = {
'Ammos': {'Crystal Geode', 'Energy Orb', 'Greater Runes', 'Inferno Orb', 'Pile of Mace Spheres', 'Quiver of Ethereal Arrows', }, 'Ammos': {'Crystal Geode', 'Energy Orb', 'Greater Runes', 'Inferno Orb', 'Pile of Mace Spheres', 'Quiver of Ethereal Arrows', },
'Armors': {'Enchanted Shield', 'Silver Shield', }, 'Armors': {'Enchanted Shield', 'Silver Shield', },
'Artifacts': {'Chaos Device', 'Morph Ovum', 'Mystic Urn', 'Quartz Flask', 'Ring of Invincibility', 'Shadowsphere', 'Timebomb of the Ancients', 'Tome of Power', 'Torch', }, 'Artifacts': {'Chaos Device', 'Morph Ovum', 'Mystic Urn', 'Quartz Flask', 'Ring of Invincibility', 'Shadowsphere', 'Timebomb of the Ancients', 'Tome of Power', 'Torch', },
'Keys': {'Ambulatory (E4M3) - Blue key', 'Ambulatory (E4M3) - Green key', 'Ambulatory (E4M3) - Yellow key', 'Blockhouse (E4M2) - Blue key', 'Blockhouse (E4M2) - Green key', 'Blockhouse (E4M2) - Yellow key', 'Catafalque (E4M1) - Green key', 'Catafalque (E4M1) - Yellow key', 'Colonnade (E5M6) - Blue key', 'Colonnade (E5M6) - Green key', 'Colonnade (E5M6) - Yellow key', 'Courtyard (E5M4) - Blue key', 'Courtyard (E5M4) - Green key', 'Courtyard (E5M4) - Yellow key', 'Foetid Manse (E5M7) - Blue key', 'Foetid Manse (E5M7) - Green key', 'Foetid Manse (E5M7) - Yellow key', 'Great Stair (E4M5) - Blue key', 'Great Stair (E4M5) - Green key', 'Great Stair (E4M5) - Yellow key', 'Halls of the Apostate (E4M6) - Blue key', 'Halls of the Apostate (E4M6) - Green key', 'Halls of the Apostate (E4M6) - Yellow key', 'Hydratyr (E5M5) - Blue key', 'Hydratyr (E5M5) - Green key', 'Hydratyr (E5M5) - Yellow key', 'Mausoleum (E4M9) - Yellow key', 'Ochre Cliffs (E5M1) - Blue key', 'Ochre Cliffs (E5M1) - Green key', 'Ochre Cliffs (E5M1) - Yellow key', 'Quay (E5M3) - Blue key', 'Quay (E5M3) - Green key', 'Quay (E5M3) - Yellow key', 'Ramparts of Perdition (E4M7) - Blue key', 'Ramparts of Perdition (E4M7) - Green key', 'Ramparts of Perdition (E4M7) - Yellow key', 'Rapids (E5M2) - Green key', 'Rapids (E5M2) - Yellow key', 'Shattered Bridge (E4M8) - Yellow key', "Skein of D'Sparil (E5M9) - Blue key", "Skein of D'Sparil (E5M9) - Green key", "Skein of D'Sparil (E5M9) - Yellow key", 'The Aquifier (E3M9) - Blue key', 'The Aquifier (E3M9) - Green key', 'The Aquifier (E3M9) - Yellow key', 'The Azure Fortress (E3M4) - Green key', 'The Azure Fortress (E3M4) - Yellow key', 'The Catacombs (E2M5) - Blue key', 'The Catacombs (E2M5) - Green key', 'The Catacombs (E2M5) - Yellow key', 'The Cathedral (E1M6) - Green key', 'The Cathedral (E1M6) - Yellow key', 'The Cesspool (E3M2) - Blue key', 'The Cesspool (E3M2) - Green key', 'The Cesspool (E3M2) - Yellow key', 'The Chasm (E3M7) - Blue key', 'The Chasm (E3M7) - Green key', 'The Chasm (E3M7) - Yellow key', 'The Citadel (E1M5) - Blue key', 'The Citadel (E1M5) - Green key', 'The Citadel (E1M5) - Yellow key', 'The Confluence (E3M3) - Blue key', 'The Confluence (E3M3) - Green key', 'The Confluence (E3M3) - Yellow key', 'The Crater (E2M1) - Green key', 'The Crater (E2M1) - Yellow key', 'The Crypts (E1M7) - Blue key', 'The Crypts (E1M7) - Green key', 'The Crypts (E1M7) - Yellow key', 'The Docks (E1M1) - Yellow key', 'The Dungeons (E1M2) - Blue key', 'The Dungeons (E1M2) - Green key', 'The Dungeons (E1M2) - Yellow key', 'The Gatehouse (E1M3) - Green key', 'The Gatehouse (E1M3) - Yellow key', 'The Glacier (E2M9) - Blue key', 'The Glacier (E2M9) - Green key', 'The Glacier (E2M9) - Yellow key', 'The Graveyard (E1M9) - Blue key', 'The Graveyard (E1M9) - Green key', 'The Graveyard (E1M9) - Yellow key', 'The Great Hall (E2M7) - Blue key', 'The Great Hall (E2M7) - Green key', 'The Great Hall (E2M7) - Yellow key', 'The Guard Tower (E1M4) - Green key', 'The Guard Tower (E1M4) - Yellow key', 'The Halls of Fear (E3M6) - Blue key', 'The Halls of Fear (E3M6) - Green key', 'The Halls of Fear (E3M6) - Yellow key', 'The Ice Grotto (E2M4) - Blue key', 'The Ice Grotto (E2M4) - Green key', 'The Ice Grotto (E2M4) - Yellow key', 'The Labyrinth (E2M6) - Blue key', 'The Labyrinth (E2M6) - Green key', 'The Labyrinth (E2M6) - Yellow key', 'The Lava Pits (E2M2) - Green key', 'The Lava Pits (E2M2) - Yellow key', 'The Ophidian Lair (E3M5) - Green key', 'The Ophidian Lair (E3M5) - Yellow key', 'The River of Fire (E2M3) - Blue key', 'The River of Fire (E2M3) - Green key', 'The River of Fire (E2M3) - Yellow key', 'The Storehouse (E3M1) - Green key', 'The Storehouse (E3M1) - Yellow key', }, 'Keys': {'Ambulatory (E4M3) - Blue key', 'Ambulatory (E4M3) - Green key', 'Ambulatory (E4M3) - Yellow key', 'Blockhouse (E4M2) - Blue key', 'Blockhouse (E4M2) - Green key', 'Blockhouse (E4M2) - Yellow key', 'Catafalque (E4M1) - Green key', 'Catafalque (E4M1) - Yellow key', 'Colonnade (E5M6) - Blue key', 'Colonnade (E5M6) - Green key', 'Colonnade (E5M6) - Yellow key', 'Courtyard (E5M4) - Blue key', 'Courtyard (E5M4) - Green key', 'Courtyard (E5M4) - Yellow key', 'Foetid Manse (E5M7) - Blue key', 'Foetid Manse (E5M7) - Green key', 'Foetid Manse (E5M7) - Yellow key', 'Great Stair (E4M5) - Blue key', 'Great Stair (E4M5) - Green key', 'Great Stair (E4M5) - Yellow key', 'Halls of the Apostate (E4M6) - Blue key', 'Halls of the Apostate (E4M6) - Green key', 'Halls of the Apostate (E4M6) - Yellow key', 'Hydratyr (E5M5) - Blue key', 'Hydratyr (E5M5) - Green key', 'Hydratyr (E5M5) - Yellow key', 'Mausoleum (E4M9) - Yellow key', 'Ochre Cliffs (E5M1) - Blue key', 'Ochre Cliffs (E5M1) - Green key', 'Ochre Cliffs (E5M1) - Yellow key', 'Quay (E5M3) - Blue key', 'Quay (E5M3) - Green key', 'Quay (E5M3) - Yellow key', 'Ramparts of Perdition (E4M7) - Blue key', 'Ramparts of Perdition (E4M7) - Green key', 'Ramparts of Perdition (E4M7) - Yellow key', 'Rapids (E5M2) - Green key', 'Rapids (E5M2) - Yellow key', 'Shattered Bridge (E4M8) - Yellow key', "Skein of D'Sparil (E5M9) - Blue key", "Skein of D'Sparil (E5M9) - Green key", "Skein of D'Sparil (E5M9) - Yellow key", 'The Aquifer (E3M9) - Blue key', 'The Aquifer (E3M9) - Green key', 'The Aquifer (E3M9) - Yellow key', 'The Azure Fortress (E3M4) - Green key', 'The Azure Fortress (E3M4) - Yellow key', 'The Catacombs (E2M5) - Blue key', 'The Catacombs (E2M5) - Green key', 'The Catacombs (E2M5) - Yellow key', 'The Cathedral (E1M6) - Green key', 'The Cathedral (E1M6) - Yellow key', 'The Cesspool (E3M2) - Blue key', 'The Cesspool (E3M2) - Green key', 'The Cesspool (E3M2) - Yellow key', 'The Chasm (E3M7) - Blue key', 'The Chasm (E3M7) - Green key', 'The Chasm (E3M7) - Yellow key', 'The Citadel (E1M5) - Blue key', 'The Citadel (E1M5) - Green key', 'The Citadel (E1M5) - Yellow key', 'The Confluence (E3M3) - Blue key', 'The Confluence (E3M3) - Green key', 'The Confluence (E3M3) - Yellow key', 'The Crater (E2M1) - Green key', 'The Crater (E2M1) - Yellow key', 'The Crypts (E1M7) - Blue key', 'The Crypts (E1M7) - Green key', 'The Crypts (E1M7) - Yellow key', 'The Docks (E1M1) - Yellow key', 'The Dungeons (E1M2) - Blue key', 'The Dungeons (E1M2) - Green key', 'The Dungeons (E1M2) - Yellow key', 'The Gatehouse (E1M3) - Green key', 'The Gatehouse (E1M3) - Yellow key', 'The Glacier (E2M9) - Blue key', 'The Glacier (E2M9) - Green key', 'The Glacier (E2M9) - Yellow key', 'The Graveyard (E1M9) - Blue key', 'The Graveyard (E1M9) - Green key', 'The Graveyard (E1M9) - Yellow key', 'The Great Hall (E2M7) - Blue key', 'The Great Hall (E2M7) - Green key', 'The Great Hall (E2M7) - Yellow key', 'The Guard Tower (E1M4) - Green key', 'The Guard Tower (E1M4) - Yellow key', 'The Halls of Fear (E3M6) - Blue key', 'The Halls of Fear (E3M6) - Green key', 'The Halls of Fear (E3M6) - Yellow key', 'The Ice Grotto (E2M4) - Blue key', 'The Ice Grotto (E2M4) - Green key', 'The Ice Grotto (E2M4) - Yellow key', 'The Labyrinth (E2M6) - Blue key', 'The Labyrinth (E2M6) - Green key', 'The Labyrinth (E2M6) - Yellow key', 'The Lava Pits (E2M2) - Green key', 'The Lava Pits (E2M2) - Yellow key', 'The Ophidian Lair (E3M5) - Green key', 'The Ophidian Lair (E3M5) - Yellow key', 'The River of Fire (E2M3) - Blue key', 'The River of Fire (E2M3) - Green key', 'The River of Fire (E2M3) - Yellow key', 'The Storehouse (E3M1) - Green key', 'The Storehouse (E3M1) - Yellow key', },
'Levels': {'Ambulatory (E4M3)', 'Blockhouse (E4M2)', 'Catafalque (E4M1)', 'Colonnade (E5M6)', 'Courtyard (E5M4)', "D'Sparil'S Keep (E3M8)", 'Field of Judgement (E5M8)', 'Foetid Manse (E5M7)', 'Great Stair (E4M5)', 'Halls of the Apostate (E4M6)', "Hell's Maw (E1M8)", 'Hydratyr (E5M5)', 'Mausoleum (E4M9)', 'Ochre Cliffs (E5M1)', 'Quay (E5M3)', 'Ramparts of Perdition (E4M7)', 'Rapids (E5M2)', 'Sepulcher (E4M4)', 'Shattered Bridge (E4M8)', "Skein of D'Sparil (E5M9)", 'The Aquifier (E3M9)', 'The Azure Fortress (E3M4)', 'The Catacombs (E2M5)', 'The Cathedral (E1M6)', 'The Cesspool (E3M2)', 'The Chasm (E3M7)', 'The Citadel (E1M5)', 'The Confluence (E3M3)', 'The Crater (E2M1)', 'The Crypts (E1M7)', 'The Docks (E1M1)', 'The Dungeons (E1M2)', 'The Gatehouse (E1M3)', 'The Glacier (E2M9)', 'The Graveyard (E1M9)', 'The Great Hall (E2M7)', 'The Guard Tower (E1M4)', 'The Halls of Fear (E3M6)', 'The Ice Grotto (E2M4)', 'The Labyrinth (E2M6)', 'The Lava Pits (E2M2)', 'The Ophidian Lair (E3M5)', 'The Portals of Chaos (E2M8)', 'The River of Fire (E2M3)', 'The Storehouse (E3M1)', }, 'Levels': {'Ambulatory (E4M3)', 'Blockhouse (E4M2)', 'Catafalque (E4M1)', 'Colonnade (E5M6)', 'Courtyard (E5M4)', "D'Sparil's Keep (E3M8)", 'Field of Judgement (E5M8)', 'Foetid Manse (E5M7)', 'Great Stair (E4M5)', 'Halls of the Apostate (E4M6)', "Hell's Maw (E1M8)", 'Hydratyr (E5M5)', 'Mausoleum (E4M9)', 'Ochre Cliffs (E5M1)', 'Quay (E5M3)', 'Ramparts of Perdition (E4M7)', 'Rapids (E5M2)', 'Sepulcher (E4M4)', 'Shattered Bridge (E4M8)', "Skein of D'Sparil (E5M9)", 'The Aquifer (E3M9)', 'The Azure Fortress (E3M4)', 'The Catacombs (E2M5)', 'The Cathedral (E1M6)', 'The Cesspool (E3M2)', 'The Chasm (E3M7)', 'The Citadel (E1M5)', 'The Confluence (E3M3)', 'The Crater (E2M1)', 'The Crypts (E1M7)', 'The Docks (E1M1)', 'The Dungeons (E1M2)', 'The Gatehouse (E1M3)', 'The Glacier (E2M9)', 'The Graveyard (E1M9)', 'The Great Hall (E2M7)', 'The Guard Tower (E1M4)', 'The Halls of Fear (E3M6)', 'The Ice Grotto (E2M4)', 'The Labyrinth (E2M6)', 'The Lava Pits (E2M2)', 'The Ophidian Lair (E3M5)', 'The Portals of Chaos (E2M8)', 'The River of Fire (E2M3)', 'The Storehouse (E3M1)', },
'Map Scrolls': {'Ambulatory (E4M3) - Map Scroll', 'Blockhouse (E4M2) - Map Scroll', 'Catafalque (E4M1) - Map Scroll', 'Colonnade (E5M6) - Map Scroll', 'Courtyard (E5M4) - Map Scroll', "D'Sparil'S Keep (E3M8) - Map Scroll", 'Field of Judgement (E5M8) - Map Scroll', 'Foetid Manse (E5M7) - Map Scroll', 'Great Stair (E4M5) - Map Scroll', 'Halls of the Apostate (E4M6) - Map Scroll', "Hell's Maw (E1M8) - Map Scroll", 'Hydratyr (E5M5) - Map Scroll', 'Mausoleum (E4M9) - Map Scroll', 'Ochre Cliffs (E5M1) - Map Scroll', 'Quay (E5M3) - Map Scroll', 'Ramparts of Perdition (E4M7) - Map Scroll', 'Rapids (E5M2) - Map Scroll', 'Sepulcher (E4M4) - Map Scroll', 'Shattered Bridge (E4M8) - Map Scroll', "Skein of D'Sparil (E5M9) - Map Scroll", 'The Aquifier (E3M9) - Map Scroll', 'The Azure Fortress (E3M4) - Map Scroll', 'The Catacombs (E2M5) - Map Scroll', 'The Cathedral (E1M6) - Map Scroll', 'The Cesspool (E3M2) - Map Scroll', 'The Chasm (E3M7) - Map Scroll', 'The Citadel (E1M5) - Map Scroll', 'The Confluence (E3M3) - Map Scroll', 'The Crater (E2M1) - Map Scroll', 'The Crypts (E1M7) - Map Scroll', 'The Docks (E1M1) - Map Scroll', 'The Dungeons (E1M2) - Map Scroll', 'The Gatehouse (E1M3) - Map Scroll', 'The Glacier (E2M9) - Map Scroll', 'The Graveyard (E1M9) - Map Scroll', 'The Great Hall (E2M7) - Map Scroll', 'The Guard Tower (E1M4) - Map Scroll', 'The Halls of Fear (E3M6) - Map Scroll', 'The Ice Grotto (E2M4) - Map Scroll', 'The Labyrinth (E2M6) - Map Scroll', 'The Lava Pits (E2M2) - Map Scroll', 'The Ophidian Lair (E3M5) - Map Scroll', 'The Portals of Chaos (E2M8) - Map Scroll', 'The River of Fire (E2M3) - Map Scroll', 'The Storehouse (E3M1) - Map Scroll', }, 'Map Scrolls': {'Ambulatory (E4M3) - Map Scroll', 'Blockhouse (E4M2) - Map Scroll', 'Catafalque (E4M1) - Map Scroll', 'Colonnade (E5M6) - Map Scroll', 'Courtyard (E5M4) - Map Scroll', "D'Sparil's Keep (E3M8) - Map Scroll", 'Field of Judgement (E5M8) - Map Scroll', 'Foetid Manse (E5M7) - Map Scroll', 'Great Stair (E4M5) - Map Scroll', 'Halls of the Apostate (E4M6) - Map Scroll', "Hell's Maw (E1M8) - Map Scroll", 'Hydratyr (E5M5) - Map Scroll', 'Mausoleum (E4M9) - Map Scroll', 'Ochre Cliffs (E5M1) - Map Scroll', 'Quay (E5M3) - Map Scroll', 'Ramparts of Perdition (E4M7) - Map Scroll', 'Rapids (E5M2) - Map Scroll', 'Sepulcher (E4M4) - Map Scroll', 'Shattered Bridge (E4M8) - Map Scroll', "Skein of D'Sparil (E5M9) - Map Scroll", 'The Aquifer (E3M9) - Map Scroll', 'The Azure Fortress (E3M4) - Map Scroll', 'The Catacombs (E2M5) - Map Scroll', 'The Cathedral (E1M6) - Map Scroll', 'The Cesspool (E3M2) - Map Scroll', 'The Chasm (E3M7) - Map Scroll', 'The Citadel (E1M5) - Map Scroll', 'The Confluence (E3M3) - Map Scroll', 'The Crater (E2M1) - Map Scroll', 'The Crypts (E1M7) - Map Scroll', 'The Docks (E1M1) - Map Scroll', 'The Dungeons (E1M2) - Map Scroll', 'The Gatehouse (E1M3) - Map Scroll', 'The Glacier (E2M9) - Map Scroll', 'The Graveyard (E1M9) - Map Scroll', 'The Great Hall (E2M7) - Map Scroll', 'The Guard Tower (E1M4) - Map Scroll', 'The Halls of Fear (E3M6) - Map Scroll', 'The Ice Grotto (E2M4) - Map Scroll', 'The Labyrinth (E2M6) - Map Scroll', 'The Lava Pits (E2M2) - Map Scroll', 'The Ophidian Lair (E3M5) - Map Scroll', 'The Portals of Chaos (E2M8) - Map Scroll', 'The River of Fire (E2M3) - Map Scroll', 'The Storehouse (E3M1) - Map Scroll', },
'Weapons': {'Dragon Claw', 'Ethereal Crossbow', 'Firemace', 'Gauntlets of the Necromancer', 'Hellstaff', 'Phoenix Rod', }, 'Weapons': {'Dragon Claw', 'Ethereal Crossbow', 'Firemace', 'Gauntlets of the Necromancer', 'Hellstaff', 'Phoenix Rod', },
} }

View File

@@ -3633,300 +3633,300 @@ location_table: Dict[int, LocationDict] = {
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "The Chasm (E3M7) Blue"}, 'region': "The Chasm (E3M7) Blue"},
371517: {'name': "D'Sparil'S Keep (E3M8) - Phoenix Rod", 371517: {'name': "D'Sparil's Keep (E3M8) - Phoenix Rod",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 55, 'index': 55,
'doom_type': 2003, 'doom_type': 2003,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371518: {'name': "D'Sparil'S Keep (E3M8) - Ethereal Crossbow", 371518: {'name': "D'Sparil's Keep (E3M8) - Ethereal Crossbow",
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 8, 'map': 8,
'index': 56, 'index': 56,
'doom_type': 2001, 'doom_type': 2001,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371519: {'name': "D'Sparil'S Keep (E3M8) - Dragon Claw", 371519: {'name': "D'Sparil's Keep (E3M8) - Dragon Claw",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 57, 'index': 57,
'doom_type': 53, 'doom_type': 53,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371520: {'name': "D'Sparil'S Keep (E3M8) - Gauntlets of the Necromancer", 371520: {'name': "D'Sparil's Keep (E3M8) - Gauntlets of the Necromancer",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 58, 'index': 58,
'doom_type': 2005, 'doom_type': 2005,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371521: {'name': "D'Sparil'S Keep (E3M8) - Hellstaff", 371521: {'name': "D'Sparil's Keep (E3M8) - Hellstaff",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 59, 'index': 59,
'doom_type': 2004, 'doom_type': 2004,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371522: {'name': "D'Sparil'S Keep (E3M8) - Bag of Holding", 371522: {'name': "D'Sparil's Keep (E3M8) - Bag of Holding",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 63, 'index': 63,
'doom_type': 8, 'doom_type': 8,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371523: {'name': "D'Sparil'S Keep (E3M8) - Mystic Urn", 371523: {'name': "D'Sparil's Keep (E3M8) - Mystic Urn",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 64, 'index': 64,
'doom_type': 32, 'doom_type': 32,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371524: {'name': "D'Sparil'S Keep (E3M8) - Ring of Invincibility", 371524: {'name': "D'Sparil's Keep (E3M8) - Ring of Invincibility",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 65, 'index': 65,
'doom_type': 84, 'doom_type': 84,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371525: {'name': "D'Sparil'S Keep (E3M8) - Shadowsphere", 371525: {'name': "D'Sparil's Keep (E3M8) - Shadowsphere",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 66, 'index': 66,
'doom_type': 75, 'doom_type': 75,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371526: {'name': "D'Sparil'S Keep (E3M8) - Silver Shield", 371526: {'name': "D'Sparil's Keep (E3M8) - Silver Shield",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 67, 'index': 67,
'doom_type': 85, 'doom_type': 85,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371527: {'name': "D'Sparil'S Keep (E3M8) - Enchanted Shield", 371527: {'name': "D'Sparil's Keep (E3M8) - Enchanted Shield",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 68, 'index': 68,
'doom_type': 31, 'doom_type': 31,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371528: {'name': "D'Sparil'S Keep (E3M8) - Tome of Power", 371528: {'name': "D'Sparil's Keep (E3M8) - Tome of Power",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': 69, 'index': 69,
'doom_type': 86, 'doom_type': 86,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371529: {'name': "D'Sparil'S Keep (E3M8) - Tome of Power 2", 371529: {'name': "D'Sparil's Keep (E3M8) - Tome of Power 2",
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 8, 'map': 8,
'index': 70, 'index': 70,
'doom_type': 86, 'doom_type': 86,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371530: {'name': "D'Sparil'S Keep (E3M8) - Chaos Device", 371530: {'name': "D'Sparil's Keep (E3M8) - Chaos Device",
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 8, 'map': 8,
'index': 71, 'index': 71,
'doom_type': 36, 'doom_type': 36,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371531: {'name': "D'Sparil'S Keep (E3M8) - Tome of Power 3", 371531: {'name': "D'Sparil's Keep (E3M8) - Tome of Power 3",
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 8, 'map': 8,
'index': 245, 'index': 245,
'doom_type': 86, 'doom_type': 86,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371532: {'name': "D'Sparil'S Keep (E3M8) - Exit", 371532: {'name': "D'Sparil's Keep (E3M8) - Exit",
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 8, 'map': 8,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "D'Sparil'S Keep (E3M8) Main"}, 'region': "D'Sparil's Keep (E3M8) Main"},
371533: {'name': 'The Aquifier (E3M9) - Blue key', 371533: {'name': 'The Aquifer (E3M9) - Blue key',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 12, 'index': 12,
'doom_type': 79, 'doom_type': 79,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371534: {'name': 'The Aquifier (E3M9) - Green key', 371534: {'name': 'The Aquifer (E3M9) - Green key',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 13, 'index': 13,
'doom_type': 73, 'doom_type': 73,
'region': "The Aquifier (E3M9) Yellow"}, 'region': "The Aquifer (E3M9) Yellow"},
371535: {'name': 'The Aquifier (E3M9) - Yellow key', 371535: {'name': 'The Aquifer (E3M9) - Yellow key',
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 9, 'map': 9,
'index': 14, 'index': 14,
'doom_type': 80, 'doom_type': 80,
'region': "The Aquifier (E3M9) Main"}, 'region': "The Aquifer (E3M9) Main"},
371536: {'name': 'The Aquifier (E3M9) - Ethereal Crossbow', 371536: {'name': 'The Aquifer (E3M9) - Ethereal Crossbow',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 141, 'index': 141,
'doom_type': 2001, 'doom_type': 2001,
'region': "The Aquifier (E3M9) Main"}, 'region': "The Aquifer (E3M9) Main"},
371537: {'name': 'The Aquifier (E3M9) - Phoenix Rod', 371537: {'name': 'The Aquifer (E3M9) - Phoenix Rod',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 142, 'index': 142,
'doom_type': 2003, 'doom_type': 2003,
'region': "The Aquifier (E3M9) Yellow"}, 'region': "The Aquifer (E3M9) Yellow"},
371538: {'name': 'The Aquifier (E3M9) - Dragon Claw', 371538: {'name': 'The Aquifer (E3M9) - Dragon Claw',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 143, 'index': 143,
'doom_type': 53, 'doom_type': 53,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371539: {'name': 'The Aquifier (E3M9) - Hellstaff', 371539: {'name': 'The Aquifer (E3M9) - Hellstaff',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 144, 'index': 144,
'doom_type': 2004, 'doom_type': 2004,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371540: {'name': 'The Aquifier (E3M9) - Gauntlets of the Necromancer', 371540: {'name': 'The Aquifer (E3M9) - Gauntlets of the Necromancer',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 145, 'index': 145,
'doom_type': 2005, 'doom_type': 2005,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371541: {'name': 'The Aquifier (E3M9) - Ring of Invincibility', 371541: {'name': 'The Aquifer (E3M9) - Ring of Invincibility',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 148, 'index': 148,
'doom_type': 84, 'doom_type': 84,
'region': "The Aquifier (E3M9) Yellow"}, 'region': "The Aquifer (E3M9) Yellow"},
371542: {'name': 'The Aquifier (E3M9) - Mystic Urn', 371542: {'name': 'The Aquifer (E3M9) - Mystic Urn',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 149, 'index': 149,
'doom_type': 32, 'doom_type': 32,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371543: {'name': 'The Aquifier (E3M9) - Silver Shield', 371543: {'name': 'The Aquifer (E3M9) - Silver Shield',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 151, 'index': 151,
'doom_type': 85, 'doom_type': 85,
'region': "The Aquifier (E3M9) Main"}, 'region': "The Aquifer (E3M9) Main"},
371544: {'name': 'The Aquifier (E3M9) - Tome of Power', 371544: {'name': 'The Aquifer (E3M9) - Tome of Power',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 152, 'index': 152,
'doom_type': 86, 'doom_type': 86,
'region': "The Aquifier (E3M9) Main"}, 'region': "The Aquifer (E3M9) Main"},
371545: {'name': 'The Aquifier (E3M9) - Bag of Holding', 371545: {'name': 'The Aquifer (E3M9) - Bag of Holding',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 153, 'index': 153,
'doom_type': 8, 'doom_type': 8,
'region': "The Aquifier (E3M9) Yellow"}, 'region': "The Aquifer (E3M9) Yellow"},
371546: {'name': 'The Aquifier (E3M9) - Morph Ovum', 371546: {'name': 'The Aquifer (E3M9) - Morph Ovum',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 154, 'index': 154,
'doom_type': 30, 'doom_type': 30,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371547: {'name': 'The Aquifier (E3M9) - Map Scroll', 371547: {'name': 'The Aquifer (E3M9) - Map Scroll',
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 9, 'map': 9,
'index': 155, 'index': 155,
'doom_type': 35, 'doom_type': 35,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371548: {'name': 'The Aquifier (E3M9) - Chaos Device', 371548: {'name': 'The Aquifer (E3M9) - Chaos Device',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 156, 'index': 156,
'doom_type': 36, 'doom_type': 36,
'region': "The Aquifier (E3M9) Yellow"}, 'region': "The Aquifer (E3M9) Yellow"},
371549: {'name': 'The Aquifier (E3M9) - Enchanted Shield', 371549: {'name': 'The Aquifer (E3M9) - Enchanted Shield',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 157, 'index': 157,
'doom_type': 31, 'doom_type': 31,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371550: {'name': 'The Aquifier (E3M9) - Tome of Power 2', 371550: {'name': 'The Aquifer (E3M9) - Tome of Power 2',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 158, 'index': 158,
'doom_type': 86, 'doom_type': 86,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371551: {'name': 'The Aquifier (E3M9) - Torch', 371551: {'name': 'The Aquifer (E3M9) - Torch',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 159, 'index': 159,
'doom_type': 33, 'doom_type': 33,
'region': "The Aquifier (E3M9) Main"}, 'region': "The Aquifer (E3M9) Main"},
371552: {'name': 'The Aquifier (E3M9) - Shadowsphere', 371552: {'name': 'The Aquifer (E3M9) - Shadowsphere',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 160, 'index': 160,
'doom_type': 75, 'doom_type': 75,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371553: {'name': 'The Aquifier (E3M9) - Silver Shield 2', 371553: {'name': 'The Aquifer (E3M9) - Silver Shield 2',
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 9, 'map': 9,
'index': 374, 'index': 374,
'doom_type': 85, 'doom_type': 85,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371554: {'name': 'The Aquifier (E3M9) - Firemace', 371554: {'name': 'The Aquifer (E3M9) - Firemace',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 478, 'index': 478,
'doom_type': 2002, 'doom_type': 2002,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371555: {'name': 'The Aquifier (E3M9) - Firemace 2', 371555: {'name': 'The Aquifer (E3M9) - Firemace 2',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 526, 'index': 526,
'doom_type': 2002, 'doom_type': 2002,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371556: {'name': 'The Aquifier (E3M9) - Firemace 3', 371556: {'name': 'The Aquifer (E3M9) - Firemace 3',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': 527, 'index': 527,
'doom_type': 2002, 'doom_type': 2002,
'region': "The Aquifier (E3M9) Green"}, 'region': "The Aquifer (E3M9) Green"},
371557: {'name': 'The Aquifier (E3M9) - Firemace 4', 371557: {'name': 'The Aquifer (E3M9) - Firemace 4',
'episode': 3, 'episode': 3,
'check_sanity': True, 'check_sanity': True,
'map': 9, 'map': 9,
'index': 528, 'index': 528,
'doom_type': 2002, 'doom_type': 2002,
'region': "The Aquifier (E3M9) Yellow"}, 'region': "The Aquifer (E3M9) Yellow"},
371558: {'name': 'The Aquifier (E3M9) - Exit', 371558: {'name': 'The Aquifer (E3M9) - Exit',
'episode': 3, 'episode': 3,
'check_sanity': False, 'check_sanity': False,
'map': 9, 'map': 9,
'index': -1, 'index': -1,
'doom_type': -1, 'doom_type': -1,
'region': "The Aquifier (E3M9) Blue"}, 'region': "The Aquifer (E3M9) Blue"},
371559: {'name': 'Catafalque (E4M1) - Yellow key', 371559: {'name': 'Catafalque (E4M1) - Yellow key',
'episode': 4, 'episode': 4,
'check_sanity': False, 'check_sanity': False,
@@ -5963,7 +5963,7 @@ location_table: Dict[int, LocationDict] = {
'map': 3, 'map': 3,
'index': 213, 'index': 213,
'doom_type': 2005, 'doom_type': 2005,
'region': "Quay (E5M3) Main"}, 'region': "Quay (E5M3) Blue"},
371850: {'name': 'Quay (E5M3) - Dragon Claw', 371850: {'name': 'Quay (E5M3) - Dragon Claw',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6145,7 +6145,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 3, 'index': 3,
'doom_type': 79, 'doom_type': 79,
'region': "Courtyard (E5M4) Main"}, 'region': "Courtyard (E5M4) Green"},
371876: {'name': 'Courtyard (E5M4) - Yellow key', 371876: {'name': 'Courtyard (E5M4) - Yellow key',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6159,7 +6159,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 21, 'index': 21,
'doom_type': 73, 'doom_type': 73,
'region': "Courtyard (E5M4) Kakis"}, 'region': "Courtyard (E5M4) Yellow"},
371878: {'name': 'Courtyard (E5M4) - Gauntlets of the Necromancer', 371878: {'name': 'Courtyard (E5M4) - Gauntlets of the Necromancer',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6187,14 +6187,14 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 87, 'index': 87,
'doom_type': 2004, 'doom_type': 2004,
'region': "Courtyard (E5M4) Kakis"}, 'region': "Courtyard (E5M4) Yellow"},
371882: {'name': 'Courtyard (E5M4) - Phoenix Rod', 371882: {'name': 'Courtyard (E5M4) - Phoenix Rod',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
'map': 4, 'map': 4,
'index': 88, 'index': 88,
'doom_type': 2003, 'doom_type': 2003,
'region': "Courtyard (E5M4) Main"}, 'region': "Courtyard (E5M4) Green"},
371883: {'name': 'Courtyard (E5M4) - Morph Ovum', 371883: {'name': 'Courtyard (E5M4) - Morph Ovum',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6229,7 +6229,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 104, 'index': 104,
'doom_type': 84, 'doom_type': 84,
'region': "Courtyard (E5M4) Kakis"}, 'region': "Courtyard (E5M4) Yellow"},
371888: {'name': 'Courtyard (E5M4) - Shadowsphere', 371888: {'name': 'Courtyard (E5M4) - Shadowsphere',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6250,14 +6250,14 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 107, 'index': 107,
'doom_type': 35, 'doom_type': 35,
'region': "Courtyard (E5M4) Kakis"}, 'region': "Courtyard (E5M4) Yellow"},
371891: {'name': 'Courtyard (E5M4) - Chaos Device', 371891: {'name': 'Courtyard (E5M4) - Chaos Device',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
'map': 4, 'map': 4,
'index': 108, 'index': 108,
'doom_type': 36, 'doom_type': 36,
'region': "Courtyard (E5M4) Main"}, 'region': "Courtyard (E5M4) Green"},
371892: {'name': 'Courtyard (E5M4) - Tome of Power', 371892: {'name': 'Courtyard (E5M4) - Tome of Power',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6278,7 +6278,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 111, 'index': 111,
'doom_type': 86, 'doom_type': 86,
'region': "Courtyard (E5M4) Kakis"}, 'region': "Courtyard (E5M4) Yellow"},
371895: {'name': 'Courtyard (E5M4) - Torch', 371895: {'name': 'Courtyard (E5M4) - Torch',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -6299,7 +6299,7 @@ location_table: Dict[int, LocationDict] = {
'map': 4, 'map': 4,
'index': 219, 'index': 219,
'doom_type': 85, 'doom_type': 85,
'region': "Courtyard (E5M4) Kakis"}, 'region': "Courtyard (E5M4) Yellow"},
371898: {'name': 'Courtyard (E5M4) - Bag of Holding 3', 371898: {'name': 'Courtyard (E5M4) - Bag of Holding 3',
'episode': 5, 'episode': 5,
'check_sanity': False, 'check_sanity': False,
@@ -7247,23 +7247,23 @@ location_name_groups: Dict[str, Set[str]] = {
'Courtyard (E5M4) - Torch', 'Courtyard (E5M4) - Torch',
'Courtyard (E5M4) - Yellow key', 'Courtyard (E5M4) - Yellow key',
}, },
"D'Sparil'S Keep (E3M8)": { "D'Sparil's Keep (E3M8)": {
"D'Sparil'S Keep (E3M8) - Bag of Holding", "D'Sparil's Keep (E3M8) - Bag of Holding",
"D'Sparil'S Keep (E3M8) - Chaos Device", "D'Sparil's Keep (E3M8) - Chaos Device",
"D'Sparil'S Keep (E3M8) - Dragon Claw", "D'Sparil's Keep (E3M8) - Dragon Claw",
"D'Sparil'S Keep (E3M8) - Enchanted Shield", "D'Sparil's Keep (E3M8) - Enchanted Shield",
"D'Sparil'S Keep (E3M8) - Ethereal Crossbow", "D'Sparil's Keep (E3M8) - Ethereal Crossbow",
"D'Sparil'S Keep (E3M8) - Exit", "D'Sparil's Keep (E3M8) - Exit",
"D'Sparil'S Keep (E3M8) - Gauntlets of the Necromancer", "D'Sparil's Keep (E3M8) - Gauntlets of the Necromancer",
"D'Sparil'S Keep (E3M8) - Hellstaff", "D'Sparil's Keep (E3M8) - Hellstaff",
"D'Sparil'S Keep (E3M8) - Mystic Urn", "D'Sparil's Keep (E3M8) - Mystic Urn",
"D'Sparil'S Keep (E3M8) - Phoenix Rod", "D'Sparil's Keep (E3M8) - Phoenix Rod",
"D'Sparil'S Keep (E3M8) - Ring of Invincibility", "D'Sparil's Keep (E3M8) - Ring of Invincibility",
"D'Sparil'S Keep (E3M8) - Shadowsphere", "D'Sparil's Keep (E3M8) - Shadowsphere",
"D'Sparil'S Keep (E3M8) - Silver Shield", "D'Sparil's Keep (E3M8) - Silver Shield",
"D'Sparil'S Keep (E3M8) - Tome of Power", "D'Sparil's Keep (E3M8) - Tome of Power",
"D'Sparil'S Keep (E3M8) - Tome of Power 2", "D'Sparil's Keep (E3M8) - Tome of Power 2",
"D'Sparil'S Keep (E3M8) - Tome of Power 3", "D'Sparil's Keep (E3M8) - Tome of Power 3",
}, },
'Field of Judgement (E5M8)': { 'Field of Judgement (E5M8)': {
'Field of Judgement (E5M8) - Bag of Holding', 'Field of Judgement (E5M8) - Bag of Holding',
@@ -7641,33 +7641,33 @@ location_name_groups: Dict[str, Set[str]] = {
"Skein of D'Sparil (E5M9) - Torch", "Skein of D'Sparil (E5M9) - Torch",
"Skein of D'Sparil (E5M9) - Yellow key", "Skein of D'Sparil (E5M9) - Yellow key",
}, },
'The Aquifier (E3M9)': { 'The Aquifer (E3M9)': {
'The Aquifier (E3M9) - Bag of Holding', 'The Aquifer (E3M9) - Bag of Holding',
'The Aquifier (E3M9) - Blue key', 'The Aquifer (E3M9) - Blue key',
'The Aquifier (E3M9) - Chaos Device', 'The Aquifer (E3M9) - Chaos Device',
'The Aquifier (E3M9) - Dragon Claw', 'The Aquifer (E3M9) - Dragon Claw',
'The Aquifier (E3M9) - Enchanted Shield', 'The Aquifer (E3M9) - Enchanted Shield',
'The Aquifier (E3M9) - Ethereal Crossbow', 'The Aquifer (E3M9) - Ethereal Crossbow',
'The Aquifier (E3M9) - Exit', 'The Aquifer (E3M9) - Exit',
'The Aquifier (E3M9) - Firemace', 'The Aquifer (E3M9) - Firemace',
'The Aquifier (E3M9) - Firemace 2', 'The Aquifer (E3M9) - Firemace 2',
'The Aquifier (E3M9) - Firemace 3', 'The Aquifer (E3M9) - Firemace 3',
'The Aquifier (E3M9) - Firemace 4', 'The Aquifer (E3M9) - Firemace 4',
'The Aquifier (E3M9) - Gauntlets of the Necromancer', 'The Aquifer (E3M9) - Gauntlets of the Necromancer',
'The Aquifier (E3M9) - Green key', 'The Aquifer (E3M9) - Green key',
'The Aquifier (E3M9) - Hellstaff', 'The Aquifer (E3M9) - Hellstaff',
'The Aquifier (E3M9) - Map Scroll', 'The Aquifer (E3M9) - Map Scroll',
'The Aquifier (E3M9) - Morph Ovum', 'The Aquifer (E3M9) - Morph Ovum',
'The Aquifier (E3M9) - Mystic Urn', 'The Aquifer (E3M9) - Mystic Urn',
'The Aquifier (E3M9) - Phoenix Rod', 'The Aquifer (E3M9) - Phoenix Rod',
'The Aquifier (E3M9) - Ring of Invincibility', 'The Aquifer (E3M9) - Ring of Invincibility',
'The Aquifier (E3M9) - Shadowsphere', 'The Aquifer (E3M9) - Shadowsphere',
'The Aquifier (E3M9) - Silver Shield', 'The Aquifer (E3M9) - Silver Shield',
'The Aquifier (E3M9) - Silver Shield 2', 'The Aquifer (E3M9) - Silver Shield 2',
'The Aquifier (E3M9) - Tome of Power', 'The Aquifer (E3M9) - Tome of Power',
'The Aquifier (E3M9) - Tome of Power 2', 'The Aquifer (E3M9) - Tome of Power 2',
'The Aquifier (E3M9) - Torch', 'The Aquifer (E3M9) - Torch',
'The Aquifier (E3M9) - Yellow key', 'The Aquifer (E3M9) - Yellow key',
}, },
'The Azure Fortress (E3M4)': { 'The Azure Fortress (E3M4)': {
'The Azure Fortress (E3M4) - Bag of Holding', 'The Azure Fortress (E3M4) - Bag of Holding',

View File

@@ -29,8 +29,8 @@ map_names: List[str] = [
'The Ophidian Lair (E3M5)', 'The Ophidian Lair (E3M5)',
'The Halls of Fear (E3M6)', 'The Halls of Fear (E3M6)',
'The Chasm (E3M7)', 'The Chasm (E3M7)',
"D'Sparil'S Keep (E3M8)", "D'Sparil's Keep (E3M8)",
'The Aquifier (E3M9)', 'The Aquifer (E3M9)',
'Catafalque (E4M1)', 'Catafalque (E4M1)',
'Blockhouse (E4M2)', 'Blockhouse (E4M2)',
'Ambulatory (E4M3)', 'Ambulatory (E4M3)',

View File

@@ -520,34 +520,34 @@ regions:List[RegionDict] = [
"episode":3, "episode":3,
"connections":[{"target":"The Chasm (E3M7) Yellow","pro":False}]}, "connections":[{"target":"The Chasm (E3M7) Yellow","pro":False}]},
# D'Sparil'S Keep (E3M8) # D'Sparil's Keep (E3M8)
{"name":"D'Sparil'S Keep (E3M8) Main", {"name":"D'Sparil's Keep (E3M8) Main",
"connects_to_hub":True, "connects_to_hub":True,
"episode":3, "episode":3,
"connections":[]}, "connections":[]},
# The Aquifier (E3M9) # The Aquifer (E3M9)
{"name":"The Aquifier (E3M9) Main", {"name":"The Aquifer (E3M9) Main",
"connects_to_hub":True, "connects_to_hub":True,
"episode":3, "episode":3,
"connections":[{"target":"The Aquifier (E3M9) Yellow","pro":False}]}, "connections":[{"target":"The Aquifer (E3M9) Yellow","pro":False}]},
{"name":"The Aquifier (E3M9) Blue", {"name":"The Aquifer (E3M9) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[]}, "connections":[]},
{"name":"The Aquifier (E3M9) Yellow", {"name":"The Aquifer (E3M9) Yellow",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[ "connections":[
{"target":"The Aquifier (E3M9) Green","pro":False}, {"target":"The Aquifer (E3M9) Green","pro":False},
{"target":"The Aquifier (E3M9) Main","pro":False}]}, {"target":"The Aquifer (E3M9) Main","pro":False}]},
{"name":"The Aquifier (E3M9) Green", {"name":"The Aquifer (E3M9) Green",
"connects_to_hub":False, "connects_to_hub":False,
"episode":3, "episode":3,
"connections":[ "connections":[
{"target":"The Aquifier (E3M9) Yellow","pro":False}, {"target":"The Aquifer (E3M9) Yellow","pro":False},
{"target":"The Aquifier (E3M9) Main","pro":False}, {"target":"The Aquifer (E3M9) Main","pro":False},
{"target":"The Aquifier (E3M9) Blue","pro":False}]}, {"target":"The Aquifer (E3M9) Blue","pro":False}]},
# Catafalque (E4M1) # Catafalque (E4M1)
{"name":"Catafalque (E4M1) Main", {"name":"Catafalque (E4M1) Main",
@@ -795,16 +795,22 @@ regions:List[RegionDict] = [
"connects_to_hub":True, "connects_to_hub":True,
"episode":5, "episode":5,
"connections":[ "connections":[
{"target":"Courtyard (E5M4) Kakis","pro":False}, {"target":"Courtyard (E5M4) Yellow","pro":False},
{"target":"Courtyard (E5M4) Blue","pro":False}]}, {"target":"Courtyard (E5M4) Blue","pro":False}]},
{"name":"Courtyard (E5M4) Blue", {"name":"Courtyard (E5M4) Blue",
"connects_to_hub":False, "connects_to_hub":False,
"episode":5, "episode":5,
"connections":[{"target":"Courtyard (E5M4) Main","pro":False}]}, "connections":[{"target":"Courtyard (E5M4) Main","pro":False}]},
{"name":"Courtyard (E5M4) Kakis", {"name":"Courtyard (E5M4) Yellow",
"connects_to_hub":False, "connects_to_hub":False,
"episode":5, "episode":5,
"connections":[{"target":"Courtyard (E5M4) Main","pro":False}]}, "connections":[
{"target":"Courtyard (E5M4) Main","pro":False},
{"target":"Courtyard (E5M4) Green","pro":False}]},
{"name":"Courtyard (E5M4) Green",
"connects_to_hub":False,
"episode":5,
"connections":[{"target":"Courtyard (E5M4) Yellow","pro":False}]},
# Hydratyr (E5M5) # Hydratyr (E5M5)
{"name":"Hydratyr (E5M5) Main", {"name":"Hydratyr (E5M5) Main",

View File

@@ -388,9 +388,9 @@ def set_episode3_rules(player, multiworld, pro):
set_rule(multiworld.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)) state.has("The Chasm (E3M7) - Green key", player, 1))
# D'Sparil'S Keep (E3M8) # D'Sparil's Keep (E3M8)
set_rule(multiworld.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("D'Sparil's Keep (E3M8)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and state.has("Dragon Claw", player, 1) and
@@ -398,23 +398,23 @@ def set_episode3_rules(player, multiworld, pro):
state.has("Firemace", player, 1) and state.has("Firemace", player, 1) and
state.has("Hellstaff", player, 1)) state.has("Hellstaff", player, 1))
# The Aquifier (E3M9) # The Aquifer (E3M9)
set_rule(multiworld.get_entrance("Hub -> The Aquifier (E3M9) Main", player), lambda state: set_rule(multiworld.get_entrance("Hub -> The Aquifer (E3M9) Main", player), lambda state:
state.has("The Aquifier (E3M9)", player, 1) and state.has("The Aquifer (E3M9)", player, 1) and
state.has("Gauntlets of the Necromancer", player, 1) and state.has("Gauntlets of the Necromancer", player, 1) and
state.has("Ethereal Crossbow", player, 1) and state.has("Ethereal Crossbow", player, 1) and
state.has("Dragon Claw", player, 1) and state.has("Dragon Claw", player, 1) and
state.has("Phoenix Rod", player, 1) and state.has("Phoenix Rod", player, 1) and
state.has("Firemace", player, 1) and state.has("Firemace", player, 1) and
state.has("Hellstaff", player, 1)) state.has("Hellstaff", player, 1))
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Main -> The Aquifier (E3M9) Yellow", player), lambda state: set_rule(multiworld.get_entrance("The Aquifer (E3M9) Main -> The Aquifer (E3M9) Yellow", player), lambda state:
state.has("The Aquifier (E3M9) - Yellow key", player, 1)) state.has("The Aquifer (E3M9) - Yellow key", player, 1))
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Green", player), lambda state: set_rule(multiworld.get_entrance("The Aquifer (E3M9) Yellow -> The Aquifer (E3M9) Green", player), lambda state:
state.has("The Aquifier (E3M9) - Green key", player, 1)) state.has("The Aquifer (E3M9) - Green key", player, 1))
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Yellow -> The Aquifier (E3M9) Main", player), lambda state: set_rule(multiworld.get_entrance("The Aquifer (E3M9) Yellow -> The Aquifer (E3M9) Main", player), lambda state:
state.has("The Aquifier (E3M9) - Yellow key", player, 1)) state.has("The Aquifer (E3M9) - Yellow key", player, 1))
set_rule(multiworld.get_entrance("The Aquifier (E3M9) Green -> The Aquifier (E3M9) Yellow", player), lambda state: set_rule(multiworld.get_entrance("The Aquifer (E3M9) Green -> The Aquifer (E3M9) Yellow", player), lambda state:
state.has("The Aquifier (E3M9) - Green key", player, 1)) state.has("The Aquifer (E3M9) - Green key", player, 1))
def set_episode4_rules(player, multiworld, pro): def set_episode4_rules(player, multiworld, pro):
@@ -623,15 +623,17 @@ def set_episode5_rules(player, multiworld, pro):
(state.has("Phoenix Rod", player, 1) or (state.has("Phoenix Rod", player, 1) or
state.has("Firemace", player, 1) or state.has("Firemace", player, 1) or
state.has("Hellstaff", player, 1))) state.has("Hellstaff", player, 1)))
set_rule(multiworld.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Kakis", player), lambda state: set_rule(multiworld.get_entrance("Courtyard (E5M4) Main -> Courtyard (E5M4) Yellow", player), lambda state:
state.has("Courtyard (E5M4) - Yellow key", player, 1) or state.has("Courtyard (E5M4) - Yellow key", player, 1))
state.has("Courtyard (E5M4) - Green key", player, 1))
set_rule(multiworld.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)) state.has("Courtyard (E5M4) - Blue key", player, 1))
set_rule(multiworld.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)) state.has("Courtyard (E5M4) - Blue key", player, 1))
set_rule(multiworld.get_entrance("Courtyard (E5M4) Kakis -> Courtyard (E5M4) Main", player), lambda state: set_rule(multiworld.get_entrance("Courtyard (E5M4) Yellow -> Courtyard (E5M4) Main", player), lambda state:
state.has("Courtyard (E5M4) - Yellow key", player, 1) or state.has("Courtyard (E5M4) - Yellow key", player, 1))
set_rule(multiworld.get_entrance("Courtyard (E5M4) Yellow -> Courtyard (E5M4) Green", player), lambda state:
state.has("Courtyard (E5M4) - Green key", player, 1))
set_rule(multiworld.get_entrance("Courtyard (E5M4) Green -> Courtyard (E5M4) Yellow", player), lambda state:
state.has("Courtyard (E5M4) - Green key", player, 1)) state.has("Courtyard (E5M4) - Green key", player, 1))
# Hydratyr (E5M5) # Hydratyr (E5M5)

View File

@@ -49,18 +49,18 @@ class HereticWorld(World):
location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()} location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
location_name_groups = Locations.location_name_groups location_name_groups = Locations.location_name_groups
starting_level_for_episode: List[str] = [ starting_level_for_episode: Dict[int, str] = {
"The Docks (E1M1)", 1: "The Docks (E1M1)",
"The Crater (E2M1)", 2: "The Crater (E2M1)",
"The Storehouse (E3M1)", 3: "The Storehouse (E3M1)",
"Catafalque (E4M1)", 4: "Catafalque (E4M1)",
"Ochre Cliffs (E5M1)" 5: "Ochre Cliffs (E5M1)"
] }
boss_level_for_episode: List[str] = [ all_boss_levels: List[str] = [
"Hell's Maw (E1M8)", "Hell's Maw (E1M8)",
"The Portals of Chaos (E2M8)", "The Portals of Chaos (E2M8)",
"D'Sparil'S Keep (E3M8)", "D'Sparil's Keep (E3M8)",
"Shattered Bridge (E4M8)", "Shattered Bridge (E4M8)",
"Field of Judgement (E5M8)" "Field of Judgement (E5M8)"
] ]
@@ -82,6 +82,7 @@ class HereticWorld(World):
def __init__(self, multiworld: MultiWorld, player: int): def __init__(self, multiworld: MultiWorld, player: int):
self.included_episodes = [1, 1, 1, 0, 0] self.included_episodes = [1, 1, 1, 0, 0]
self.location_count = 0 self.location_count = 0
self.starting_levels = []
super().__init__(multiworld, player) super().__init__(multiworld, player)
@@ -100,6 +101,14 @@ class HereticWorld(World):
if self.get_episode_count() == 0: if self.get_episode_count() == 0:
self.included_episodes[0] = 1 self.included_episodes[0] = 1
self.starting_levels = [level_name for (episode, level_name) in self.starting_level_for_episode.items()
if self.included_episodes[episode - 1]]
# For Solo Episode 1, place the Yellow Key for E1M1 early.
# Gives the generator five potential placements (plus the forced key) instead of only two.
if self.get_episode_count() == 1 and self.included_episodes[0]:
self.multiworld.early_items[self.player]["The Docks (E1M1) - Yellow key"] = 1
def create_regions(self): def create_regions(self):
pro = self.options.pro.value pro = self.options.pro.value
check_sanity = self.options.check_sanity.value check_sanity = self.options.check_sanity.value
@@ -154,7 +163,7 @@ class HereticWorld(World):
def completion_rule(self, state: CollectionState): def completion_rule(self, state: CollectionState):
goal_levels = Maps.map_names goal_levels = Maps.map_names
if self.options.goal.value: if self.options.goal.value:
goal_levels = self.boss_level_for_episode goal_levels = self.all_boss_levels
for map_name in goal_levels: for map_name in goal_levels:
if map_name + " - Exit" not in self.location_name_to_id: if map_name + " - Exit" not in self.location_name_to_id:
@@ -203,7 +212,7 @@ class HereticWorld(World):
if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]: if item["episode"] != -1 and not self.included_episodes[item["episode"] - 1]:
continue continue
count = item["count"] if item["name"] not in self.starting_level_for_episode else item["count"] - 1 count = item["count"] if item["name"] not in self.starting_levels else item["count"] - 1
itempool += [self.create_item(item["name"]) for _ in range(count)] itempool += [self.create_item(item["name"]) for _ in range(count)]
# Bag(s) of Holding based on options # Bag(s) of Holding based on options
@@ -236,9 +245,8 @@ class HereticWorld(World):
self.location_count -= 1 self.location_count -= 1
# Give starting levels right away # Give starting levels right away
for i in range(len(self.included_episodes)): for map_name in self.starting_levels:
if self.included_episodes[i]: self.multiworld.push_precollected(self.create_item(map_name))
self.multiworld.push_precollected(self.create_item(self.starting_level_for_episode[i]))
# Give Computer area maps if option selected # Give Computer area maps if option selected
if self.options.start_with_map_scrolls.value: if self.options.start_with_map_scrolls.value:

View File

@@ -8,8 +8,14 @@
## Installing the Archipelago Mod using Lumafly ## Installing the Archipelago Mod using Lumafly
1. Launch Lumafly and ensure it locates your Hollow Knight installation directory. 1. Launch Lumafly and ensure it locates your Hollow Knight installation directory.
2. Click the "Install" button near the "Archipelago" mod entry. 2. Install the Archipelago mods by doing either of the following:
* If desired, also install "Archipelago Map Mod" to use as an in-game tracker. * Click one of the links below to allow Lumafly to install the mods. Lumafly will prompt for confirmation.
* [Archipelago and dependencies only](https://themulhima.github.io/Lumafly/commands/download/?mods=Archipelago)
* [Archipelago with rando essentials](https://themulhima.github.io/Lumafly/commands/download/?mods=Archipelago/Archipelago%20Map%20Mod/RecentItemsDisplay/DebugMod/RandoStats/Additional%20Timelines/CompassAlwaysOn/AdditionalMaps/)
(includes Archipelago Map Mod, RecentItemsDisplay, DebugMod, RandoStats, AdditionalTimelines, CompassAlwaysOn,
and AdditionalMaps).
* Click the "Install" button near the "Archipelago" mod entry. If desired, also install "Archipelago Map Mod"
to use as an in-game tracker.
3. Launch the game, you're all set! 3. Launch the game, you're all set!
### What to do if Lumafly fails to find your installation directory ### What to do if Lumafly fails to find your installation directory

View File

@@ -295,6 +295,6 @@ def launch():
parser = get_base_parser(description="KH1 Client, for text interfacing.") parser = get_base_parser(description="KH1 Client, for text interfacing.")
args, rest = parser.parse_known_args() args, rest = parser.parse_known_args()
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main(args)) asyncio.run(main(args))
colorama.deinit() colorama.deinit()

View File

@@ -981,6 +981,6 @@ def launch():
parser = get_base_parser(description="KH2 Client, for text interfacing.") parser = get_base_parser(description="KH2 Client, for text interfacing.")
args, rest = parser.parse_known_args() args, rest = parser.parse_known_args()
colorama.init() colorama.just_fix_windows_console()
asyncio.run(main(args)) asyncio.run(main(args))
colorama.deinit() colorama.deinit()

View File

View File

@@ -1 +1 @@
Pymem>=1.10.0 Pymem>=1.10.0

View File

@@ -107,6 +107,7 @@ SYNONYMS = {
'JUMP': 'FEATHER', 'JUMP': 'FEATHER',
'PLUME': 'FEATHER', 'PLUME': 'FEATHER',
'WING': 'FEATHER', 'WING': 'FEATHER',
"QUILL": "FEATHER",
# SHOVEL # SHOVEL
'DIG': 'SHOVEL', 'DIG': 'SHOVEL',
@@ -343,6 +344,8 @@ SYNONYMS = {
# TRADING_ITEM_LETTER # TRADING_ITEM_LETTER
'CARD': 'TRADING_ITEM_LETTER', 'CARD': 'TRADING_ITEM_LETTER',
'MESSAGE': 'TRADING_ITEM_LETTER', 'MESSAGE': 'TRADING_ITEM_LETTER',
"TICKET": 'TRADING_ITEM_LETTER',
"PASS": 'TRADING_ITEM_LETTER',
# TRADING_ITEM_BROOM # TRADING_ITEM_BROOM
'SWEEP': 'TRADING_ITEM_BROOM', 'SWEEP': 'TRADING_ITEM_BROOM',
@@ -365,6 +368,8 @@ SYNONYMS = {
'MIRROR': 'TRADING_ITEM_MAGNIFYING_GLASS', 'MIRROR': 'TRADING_ITEM_MAGNIFYING_GLASS',
'SCOPE': 'TRADING_ITEM_MAGNIFYING_GLASS', 'SCOPE': 'TRADING_ITEM_MAGNIFYING_GLASS',
'XRAY': 'TRADING_ITEM_MAGNIFYING_GLASS', 'XRAY': 'TRADING_ITEM_MAGNIFYING_GLASS',
"DETECTOR": 'TRADING_ITEM_MAGNIFYING_GLASS',
"ITEMFINDER": 'TRADING_ITEM_MAGNIFYING_GLASS',
# PIECE_OF_POWER # PIECE_OF_POWER
'TRIANGLE': 'PIECE_OF_POWER', 'TRIANGLE': 'PIECE_OF_POWER',
@@ -378,6 +383,7 @@ PHRASES = {
'BOSS KEY': 'NIGHTMARE_KEY', 'BOSS KEY': 'NIGHTMARE_KEY',
'HEART PIECE': 'HEART_PIECE', 'HEART PIECE': 'HEART_PIECE',
'PIECE OF HEART': 'HEART_PIECE', 'PIECE OF HEART': 'HEART_PIECE',
"ROCK SMASH": 'BOMB',
} }
# All following will only be used to match items for the specific game. # All following will only be used to match items for the specific game.
@@ -404,6 +410,16 @@ GAME_SPECIFIC_PHRASES = {
'Ocarina of Time': { 'Ocarina of Time': {
'COJIRO': 'ROOSTER', 'COJIRO': 'ROOSTER',
"Goron Tunic": "RED_TUNIC",
"Zora Tunic": "BLUE_TUNIC",
"Wallet": "MAGIC_POWDER",
"Medallion": "PIECE_OF_POWER",
"Kokiri Emerald": "RUPEES_500",
"Goron Ruby": "RUPEES_500",
"Zora Sapphire": "RUPEES_500",
"Dins Fire": "MAGIC_ROD", # Fire shield
"Nayrus Love": "MAGIC_ROD", # Protective barrier
"Farores Wind": "MAGIC_ROD", # Create/use warp point in dungeons
}, },
'SMZ3': { 'SMZ3': {
@@ -417,10 +433,14 @@ GAME_SPECIFIC_PHRASES = {
'Sonic Adventure 2 Battle': { 'Sonic Adventure 2 Battle': {
'CHAOS EMERALD': 'PIECE_OF_POWER', 'CHAOS EMERALD': 'PIECE_OF_POWER',
"Rings": "RUPEES_20", # This should only affect filler Rings currency, not Flame Ring upgrade
"Grapes": "TRADING_ITEM_PINEAPPLE",
"Pick Nails": "SHOVEL", # Digging upgrade
}, },
'Super Mario 64': { 'Super Mario 64': {
'POWER STAR': 'PIECE_OF_POWER', 'POWER STAR': 'PIECE_OF_POWER',
"Key": "NIGHTMARE_KEY" # Affect 2nd Floor / Basement / Progressive keys
}, },
'Super Mario World': { 'Super Mario World': {
@@ -528,4 +548,336 @@ GAME_SPECIFIC_PHRASES = {
'2500 Tokens': 'RUPEES_500', '2500 Tokens': 'RUPEES_500',
'5000 Tokens': 'RUPEES_500', '5000 Tokens': 'RUPEES_500',
}, },
"Donkey Kong Country 3": {
"Flupperius Petallus Pongus": "TRADING_ITEM_HIBISCUS", # It's a flower in the game
"Banana Bird": "ROOSTER", # Made sure this is a BIRD, not a BANANA
},
"Pokemon Red and Blue": {
# Key Items
"Old Amber": "STONE_BEAK", # Aerodactyl's fossil should still be a fossil
"Coin Case": "MAGIC_POWDER", # This shouldn't spawn as RUPEES
"Bike Voucher": "TRADING_ITEM_LETTER",
"Oak's Parcel": "TRADING_ITEM_LETTER",
# Drinks always get converted to MEDICINE
"Soda Pop": "MEDICINE",
"Fresh Water": "MEDICINE",
# Consumables
"Elixir": "MEDICINE",
"Ether": "MEDICINE",
"Antidote": "MEDICINE",
"Awakening": "MEDICINE",
"Burn Heal": "MEDICINE",
"Ice Heal": "MEDICINE",
"Paralyze Heal": "MEDICINE",
"Full Heal": "MEDICINE",
"Full Restore": "MEDICINE",
},
"Pokemon Emerald": {
"Coin Case": "MAGIC_POWDER", # This shouldn't spawn as RUPEES
# Drinks always get converted to MEDICINE
"Soda Pop": "MEDICINE",
"Fresh Water": "MEDICINE",
# Consumables
"Elixir": "MEDICINE",
"Ether": "MEDICINE",
"Antidote": "MEDICINE",
"Awakening": "MEDICINE",
"Burn Heal": "MEDICINE",
"Ice Heal": "MEDICINE",
"Paralyze Heal": "MEDICINE",
"Full Heal": "MEDICINE",
"Full Restore": "MEDICINE",
"Nanab Berry": "TRADING_ITEM_BANANAS", # Special exception for Nanab Berry, which look like bananas
"Berry": "TRADING_ITEM_PINEAPPLE",
"Mail": "TRADING_ITEM_LETTER", # Snail mail, not chain mail
},
"Mario & Luigi Superstar Saga": {
# Key Items
"Peach's Extra Dress": "RED_TUNIC",
"Peasley's Rose": "TRADING_ITEM_HIBISCUS",
"Beanstar": "PIECE_OF_POWER", # Hits both Fake Beanstar and pieces of the real Beanstar, hopefully
"Beanstone": "RUPEES_500", # They're gemstones
"Firebrand": "POWER_BRACELET", # Magic power that affects Mario/Luigi's hands, either this or MAGIC_ROD would be okay
"Thunderhand": "POWER_BRACELET", # Ditto
# 1-UP Super fix
"1-UP Super": "TOADSTOOL",
# Drinks --> medicine
# Syrup bottles
"Syrup": "MEDICINE",
# Coffee blends
"Hoolumbian": "MEDICINE",
"Chuckoccino": "MEDICINE",
"Teeheespresso": "MEDICINE",
"Blend": "MEDICINE", # for all coffee blends
# Secret Scrolls --> MESSAGE
"Secret Scroll": "TRADING_ITEM_LETTER",
# Goblets --> MEDICINE
"Goblet": "MEDICINE",
# Pearl Beans --> Fruit
"Pearl Bean": 'TRADING_ITEM_PINEAPPLE',
# Bros. Armor --> Blue Tunic
"Pants": "BLUE_TUNIC",
"Jeans": "BLUE_TUNIC",
"Trousers": "BLUE_TUNIC",
"Slacks": "BLUE_TUNIC",
"Casual Coral": "BLUE_TUNIC",
"Shroom Bells": "BLUE_TUNIC",
# Badges --> Ribbon
"Badge": "TRADING_ITEM_RIBBON",
"Soulful Bros.": "TRADING_ITEM_RIBBON",
"Bros. Rock": "TRADING_ITEM_RIBBON",
# Misc. Beans --> Acorns
"Hoo Bean": "GUARDIAN_ACORN", # Beans and nuts are similar enough, right?
"Chuckle Bean": "GUARDIAN_ACORN",
"Hee Bean": "GUARDIAN_ACORN",
"Woo Bean": "GUARDIAN_ACORN",
},
"DOOM 1993": {
"Keycard": "KEY",
"Computer area map": "MAP",
"Box of": "SINGLE_ARROW", # bullets, rockets, or shotgun shells
"Energy cell pack": "SINGLE_ARROW",
"Chainsaw": "SWORD",
"Medikit": "MEDICINE",
"Skull key": "NIGHTMARE_KEY",
},
"DOOM II": {
"Keycard": "KEY",
"Computer area map": "MAP",
"Box of": "SINGLE_ARROW", # bullets, rockets, or shotgun shells
"Energy cell pack": "SINGLE_ARROW",
"Chainsaw": "SWORD",
"Medikit": "MEDICINE",
"Skull key": "NIGHTMARE_KEY",
},
"Inscryption": {
"Extra Candle": "HEART_CONTAINER", # Candles act as extra health
"Magnificus Eye": "TRADING_ITEM_MAGNIFYING_GLASS", # Needed to see hidden drawings / messages
"Monocle": "TRADING_ITEM_MAGNIFYING_GLASS", # Ditto
"Pile Of Meat": "TRADING_ITEM_DOG_FOOD",
"Angler Hook": "TRADING_ITEM_FISHING_HOOK", # Good fish.
"Currency": "RUPEES_20",
},
"Minecraft": {
"Progressive Weapons": "SWORD",
"Progressive Tools": "SHOVEL",
"Archery": "BOW",
"Emerald": "RUPEES_20",
"Brewing": "MEDICINE",
"Spyglass": 'TRADING_ITEM_MAGNIFYING_GLASS',
"Porkchop": "TRADING_ITEM_DOG_FOOD"
},
"VVVVVV": {
"Trinket": "PIECE_OF_POWER",
},
"A Hat in Time": {
"Time Piece": "PIECE_OF_POWER",
"Metro Ticket": "TRADING_ITEM_LETTER",
"Snatcher's Contract": "TRADING_ITEM_LETTER",
"Pon": "RUPEES_20",
},
"Kingdom Hearts 2": {
# Goal items / Collectibles
"Proof of": "PIECE_OF_POWER",
"Lucky Emblem": "PIECE_OF_POWER",
"Secret Ansem's Report": "TRADING_ITEM_LETTER",
# Sora Keyblades
"Bond of Flame": "SWORD",
"Circle of Life": "SWORD",
"Decisive Pumpkin": "SWORD",
"Fatal Crest": "SWORD",
"Fenrir": "SWORD",
"Follow the Wind": "SWORD",
"Guardian Soul": "SWORD",
"Gull Wing": "SWORD",
"Hero's Crest": "SWORD",
"Hidden Dragon": "SWORD",
"Monochrome": "SWORD",
"Mysterious Abyss": "SWORD",
"Oathkeeper": "SWORD",
"Oblivion": "SWORD",
"Photon Debugger": "SWORD",
"Pureblood": "SWORD",
"Rumbling Rose": "SWORD",
"Sleeping Lion": "SWORD",
"Star Seeker": "SWORD",
"Sweet Memories": "SWORD",
"Two Become One": "SWORD",
"Ultima Weapon": "SWORD",
"Winner's Proof": "SWORD",
"Wishing Lamp": "SWORD",
# Donald Staves
"Centurion+": "MAGIC_ROD",
"Nobody Lance": "MAGIC_ROD",
"Precious Mushroom": "MAGIC_ROD",
"Precious Mushroom+": "MAGIC_ROD",
"Premium Mushroom": "MAGIC_ROD",
"Rising Dragon": "MAGIC_ROD",
"Save The Queen+": "MAGIC_ROD",
"Shaman's Relic": "MAGIC_ROD",
"Victory Bell": "MAGIC_ROD",
# Goofy Shields
"Akashic Record": "SHIELD",
"Frozen Pride+": "SHIELD",
"Majestic Mushroom": "SHIELD",
"Majestic Mushroom+": "SHIELD",
"Nobody Guard": "SHIELD",
"Ogre Shield": "SHIELD",
"Save The King+": "SHIELD",
"Ultimate Mushroom": "SHIELD",
# Accessories as RIBBON
"Star Charm": "TRADING_ITEM_RIBBON",
"Ring": "TRADING_ITEM_RIBBON",
"Earring": "TRADING_ITEM_RIBBON",
"Shadow Archive": "TRADING_ITEM_RIBBON",
"Shadow Archive+": "TRADING_ITEM_RIBBON",
"Full Bloom": "TRADING_ITEM_RIBBON",
"Full Bloom+": "TRADING_ITEM_RIBBON",
# Armor as BLUE_TUNIC
"Bandanna": "BLUE_TUNIC",
"Belt": "BLUE_TUNIC",
"Band": "BLUE_TUNIC",
"Bangle": "BLUE_TUNIC",
"Armlet": "BLUE_TUNIC",
"Trinket": "BLUE_TUNIC",
"Charm": "BLUE_TUNIC",
"Anklet": "BLUE_TUNIC",
"Chain": "BLUE_TUNIC",
"Acrisius": "BLUE_TUNIC",
"Ribbon": "BLUE_TUNIC",
# Magic
"Element": "MAGIC_ROD",
# Other
"Munny Pouch": "MAGIC_POWDER",
"Ether": "MEDICINE",
"Elixir": "MEDICINE",
"Megalixir": "MEDICINE",
},
"Kingdom Hearts": {
# Goal/collectible items
"Ansem's Report": "TRADING_ITEM_LETTER",
# Dalmatian puppies
"Puppy": "BOWWOW",
"Puppies": "BOWWOW",
# Sora Keyblades
"Jungle King": "SWORD",
"Three Wishes": "SWORD",
"Fairy Harp": "SWORD",
"Pumpkinhead": "SWORD",
"Crabclaw": "SWORD",
"Divine Rose": "SWORD",
"Spellbinder": "SWORD",
"Olympia": "SWORD",
"Lionheart": "SWORD",
"Metal Chocobo": "SWORD",
"Oathkeeper": "SWORD",
"Oblivion": "SWORD",
"Lady Luck": "SWORD",
"Wishing Star": "SWORD",
"Ultima Weapon": "SWORD",
"Diamond Dust": "SWORD",
"One-Winged Angel": "SWORD",
# Donald Staves
"Morning Star": "MAGIC_ROD",
"Shooting Star": "MAGIC_ROD",
"Warhammer": "MAGIC_ROD",
"Silver Mallet": "MAGIC_ROD",
"Grand Mallet": "MAGIC_ROD",
"Lord Fortune": "MAGIC_ROD",
"Violetta": "MAGIC_ROD",
"Save the Queen": "MAGIC_ROD",
"Wizard's Relic": "MAGIC_ROD",
"Meteor Strike": "MAGIC_ROD",
"Fantasista": "MAGIC_ROD",
# Goofy Shields
"Smasher": "SHIELD",
"Gigas Fist": "SHIELD",
"Save the King": "SHIELD",
"Defender": "SHIELD",
"Seven Elements": "SHIELD",
# Magic
"Progressive Fire": "MAGIC_ROD",
"Progressive Blizzard": "MAGIC_ROD",
"Progressive Thunder": "MAGIC_ROD",
"Progressive Cure": "MAGIC_ROD",
"Progressive Gravity": "MAGIC_ROD",
"Progressive Stop": "MAGIC_ROD",
"Progressive Aero": "MAGIC_ROD",
# Accessories / armor (Let's go with BLUE_TUNIC for these, these items are closer to RPG armor anyways)
"Chain": "BLUE_TUNIC",
"Ring": "BLUE_TUNIC",
"Band": "BLUE_TUNIC",
"Three Stars": "BLUE_TUNIC",
"Stud": "BLUE_TUNIC",
"Earring": "BLUE_TUNIC",
"Bangle": "BLUE_TUNIC",
"Armlet": "BLUE_TUNIC",
"Moogle Badge": "BLUE_TUNIC",
"Cosmic Arts": "BLUE_TUNIC",
"Heartguard": "BLUE_TUNIC",
"Crystal Crown": "BLUE_TUNIC",
"Ribbon": "BLUE_TUNIC",
"Brave Warrior": "BLUE_TUNIC",
"Ifrit's Horn": "BLUE_TUNIC",
"White Fang": "BLUE_TUNIC",
"Ray of Light": "BLUE_TUNIC",
"Circlet": "BLUE_TUNIC",
"Raven's Claw": "BLUE_TUNIC",
"Omega Arts": "BLUE_TUNIC",
"Royal Crown": "BLUE_TUNIC",
"Prime Cap": "BLUE_TUNIC",
"Belt": "BLUE_TUNIC",
"EXP Bracelet": "BLUE_TUNIC",
"EXP Necklace": "BLUE_TUNIC",
# Other
"Glide": "FEATHER",
"Ether": "MEDICINE",
"Elixir": "MEDICINE",
"Megalixir": "MEDICINE",
}
} }

View File

View File

View File

View File

@@ -310,7 +310,8 @@ class LinksAwakeningWorld(World):
def opens_new_regions(item): def opens_new_regions(item):
collection_state = base_collection_state.copy() collection_state = base_collection_state.copy()
collection_state.collect(item) collection_state.collect(item, prevent_sweep=True)
collection_state.sweep_for_advancements(self.get_locations())
return len(collection_state.reachable_regions[self.player]) > reachable_count return len(collection_state.reachable_regions[self.player]) > reachable_count
start_items = [item for item in itempool if is_possible_start_item(item)] start_items = [item for item in itempool if is_possible_start_item(item)]
@@ -329,7 +330,7 @@ class LinksAwakeningWorld(World):
if entrance_mapping['start_house'] not in ['start_house', 'shop']: if entrance_mapping['start_house'] not in ['start_house', 'shop']:
start_items = [item for item in start_items if item.name != 'Shovel'] start_items = [item for item in start_items if item.name != 'Shovel']
base_collection_state = CollectionState(self.multiworld) base_collection_state = CollectionState(self.multiworld)
base_collection_state.update_reachable_regions(self.player) base_collection_state.sweep_for_advancements(self.get_locations())
reachable_count = len(base_collection_state.reachable_regions[self.player]) reachable_count = len(base_collection_state.reachable_regions[self.player])
start_item = next((item for item in start_items if opens_new_regions(item)), None) start_item = next((item for item in start_items if opens_new_regions(item)), None)
@@ -444,7 +445,7 @@ class LinksAwakeningWorld(World):
phrases.update(ItemIconGuessing.GAME_SPECIFIC_PHRASES[foreign_game]) phrases.update(ItemIconGuessing.GAME_SPECIFIC_PHRASES[foreign_game])
for phrase, icon in phrases.items(): for phrase, icon in phrases.items():
if phrase in uppered: if phrase.upper() in uppered:
return icon return icon
# pattern for breaking down camelCase, also separates out digits # pattern for breaking down camelCase, also separates out digits
pattern = re.compile(r"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=\d)") pattern = re.compile(r"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-zA-Z])(?=\d)")

View File

View File

@@ -100,6 +100,8 @@
# paintings is an array of paintings in the room. This is used for painting # paintings is an array of paintings in the room. This is used for painting
# shuffling. # shuffling.
# - id: The internal painting ID from the LINGO map. # - id: The internal painting ID from the LINGO map.
# - display_name: The name of the painting location when showed in the
# tracker. Not needed for disabled paintings.
# - enter_only: If true, painting shuffling will not place a warp exit on # - enter_only: If true, painting shuffling will not place a warp exit on
# this painting. # this painting.
# - exit_only: If true, painting shuffling will not place a warp entrance # - exit_only: If true, painting shuffling will not place a warp entrance
@@ -226,6 +228,7 @@
- HIDDEN - HIDDEN
paintings: paintings:
- id: arrows_painting - id: arrows_painting
display_name: Overhead Painting
exit_only: True exit_only: True
orientation: south orientation: south
- id: arrows_painting2 - id: arrows_painting2
@@ -234,7 +237,24 @@
- id: arrows_painting3 - id: arrows_painting3
disable: True disable: True
move: True move: True
- id: symmetry_painting_a_starter
display_name: Left Near Painting
enter_only: True
orientation: west
move: True
required_door:
room: The Wondrous (Doorknob)
door: Painting Shortcut
- id: eyes_yellow_painting2
display_name: Left Far Painting
enter_only: True
orientation: west
move: True
required_door:
room: Outside The Agreeable
door: Painting Shortcut
- id: garden_painting_tower2 - id: garden_painting_tower2
display_name: Front Left Painting
enter_only: True enter_only: True
orientation: north orientation: north
move: True move: True
@@ -242,20 +262,15 @@
room: Hedge Maze room: Hedge Maze
door: Painting Shortcut door: Painting Shortcut
- id: flower_painting_8 - id: flower_painting_8
display_name: Front Right Painting
enter_only: True enter_only: True
orientation: north orientation: north
move: True move: True
required_door: required_door:
room: Courtyard room: Courtyard
door: Painting Shortcut door: Painting Shortcut
- id: symmetry_painting_a_starter
enter_only: True
orientation: west
move: True
required_door:
room: The Wondrous (Doorknob)
door: Painting Shortcut
- id: pencil_painting6 - id: pencil_painting6
display_name: Right Far Painting
enter_only: True enter_only: True
orientation: east orientation: east
move: True move: True
@@ -263,19 +278,13 @@
room: Outside The Bold room: Outside The Bold
door: Painting Shortcut door: Painting Shortcut
- id: blueman_painting_3 - id: blueman_painting_3
display_name: Right Near Painting
enter_only: True enter_only: True
orientation: east orientation: east
move: True move: True
required_door: required_door:
room: Outside The Undeterred room: Outside The Undeterred
door: Painting Shortcut door: Painting Shortcut
- id: eyes_yellow_painting2
enter_only: True
orientation: west
move: True
required_door:
room: Outside The Agreeable
door: Painting Shortcut
Hidden Room: Hidden Room:
entrances: entrances:
Starting Room: Starting Room:
@@ -340,6 +349,7 @@
- OPEN - OPEN
paintings: paintings:
- id: owl_painting - id: owl_painting
display_name: Painting
orientation: north orientation: north
The Seeker: The Seeker:
entrances: entrances:
@@ -599,6 +609,7 @@
- OPEN - OPEN
paintings: paintings:
- id: maze_painting - id: maze_painting
display_name: Near Traveled Painting
orientation: west orientation: west
sunwarps: sunwarps:
- dots: 1 - dots: 1
@@ -630,6 +641,7 @@
door: Eights door: Eights
paintings: paintings:
- id: smile_painting_6 - id: smile_painting_6
display_name: Painting
orientation: north orientation: north
Sunwarps: Sunwarps:
# This is a special, meta-ish room. # This is a special, meta-ish room.
@@ -968,6 +980,7 @@
required_door: required_door:
door: Eye Wall door: Eye Wall
- id: smile_painting_4 - id: smile_painting_4
display_name: Near Discerning Painting
orientation: south orientation: south
sunwarps: sunwarps:
- dots: 1 - dots: 1
@@ -1068,6 +1081,7 @@
tag: midwhite tag: midwhite
paintings: paintings:
- id: west_afar - id: west_afar
display_name: Painting
orientation: south orientation: south
The Tenacious: The Tenacious:
entrances: entrances:
@@ -1392,6 +1406,7 @@
- RIGHT - RIGHT
paintings: paintings:
- id: eyes_yellow_painting - id: eyes_yellow_painting
display_name: Near Hallway Painting
orientation: east orientation: east
sunwarps: sunwarps:
- dots: 6 - dots: 6
@@ -1451,6 +1466,7 @@
- FIRE - FIRE
paintings: paintings:
- id: pencil_painting7 - id: pencil_painting7
display_name: Compass Room Painting
orientation: north orientation: north
Dread Hallway: Dread Hallway:
entrances: entrances:
@@ -1698,6 +1714,7 @@
- GAZE - GAZE
paintings: paintings:
- id: garden_painting_tower - id: garden_painting_tower
display_name: Painting
orientation: north orientation: north
The Fearless (First Floor): The Fearless (First Floor):
entrances: entrances:
@@ -2077,6 +2094,7 @@
panel: A panel: A
paintings: paintings:
- id: crown_painting - id: crown_painting
display_name: Near Achievement Painting
orientation: east orientation: east
Eight Alcove: Eight Alcove:
entrances: entrances:
@@ -2088,6 +2106,7 @@
door: Eight Door (Outside The Initiated) door: Eight Door (Outside The Initiated)
paintings: paintings:
- id: eight_painting2 - id: eight_painting2
display_name: Eight Alcove Painting
orientation: north orientation: north
Eight Room: Eight Room:
entrances: entrances:
@@ -2108,6 +2127,7 @@
tag: forbid tag: forbid
paintings: paintings:
- id: eight_painting - id: eight_painting
display_name: Eight Room Painting
orientation: south orientation: south
exit_only: True exit_only: True
required: True required: True
@@ -2340,8 +2360,10 @@
panel: YELLOW panel: YELLOW
paintings: paintings:
- id: arrows_painting_6 - id: arrows_painting_6
display_name: Left Painting
orientation: east orientation: east
- id: flower_painting_5 - id: flower_painting_5
display_name: Right Painting
orientation: south orientation: south
sunwarps: sunwarps:
- dots: 2 - dots: 2
@@ -2430,6 +2452,7 @@
door: Eights door: Eights
paintings: paintings:
- id: smile_painting_8 - id: smile_painting_8
display_name: Hot Crusts Painting
orientation: north orientation: north
sunwarps: sunwarps:
- dots: 2 - dots: 2
@@ -2531,10 +2554,13 @@
- SIZE (Big) - SIZE (Big)
paintings: paintings:
- id: hi_solved_painting3 - id: hi_solved_painting3
display_name: Cellar Replica Painting
orientation: south orientation: south
- id: hi_solved_painting2 - id: hi_solved_painting2
display_name: Cellar Painting
orientation: south orientation: south
- id: east_afar - id: east_afar
display_name: Seasons Area Painting
orientation: north orientation: north
Orange Tower Sixth Floor: Orange Tower Sixth Floor:
entrances: entrances:
@@ -2546,25 +2572,35 @@
painting: True painting: True
paintings: paintings:
- id: arrows_painting_10 - id: arrows_painting_10
display_name: Back Left Painting
orientation: east orientation: east
- id: owl_painting_3
orientation: north
- id: clock_painting
orientation: west
- id: scenery_painting_5d_2 - id: scenery_painting_5d_2
display_name: Left Near Painting
orientation: south orientation: south
- id: symmetry_painting_b_7
orientation: north
- id: panda_painting_2 - id: panda_painting_2
display_name: Left Middle Painting
orientation: south orientation: south
- id: crown_painting2
orientation: north
- id: colors_painting2 - id: colors_painting2
display_name: Left Far Painting
orientation: south orientation: south
- id: cherry_painting2 - id: clock_painting
orientation: east display_name: Front Left Painting
- id: hi_solved_painting
orientation: west orientation: west
- id: hi_solved_painting
display_name: Front Right Painting
orientation: west
- id: crown_painting2
display_name: Right Far Painting
orientation: north
- id: owl_painting_3
display_name: Right Middle Painting
orientation: north
- id: symmetry_painting_b_7
display_name: Right Near Painting
orientation: north
- id: cherry_painting2
display_name: Back Right Painting
orientation: east
Ending Area: Ending Area:
entrances: entrances:
Orange Tower Sixth Floor: Orange Tower Sixth Floor:
@@ -2660,6 +2696,7 @@
panel: MASTERY panel: MASTERY
paintings: paintings:
- id: map_painting2 - id: map_painting2
display_name: Painting
orientation: north orientation: north
enter_only: True # otherwise you might just skip the whole game! enter_only: True # otherwise you might just skip the whole game!
req_blocked_when_no_doors: True # owl hallway in vanilla doors req_blocked_when_no_doors: True # owl hallway in vanilla doors
@@ -2755,6 +2792,7 @@
non_counting: True non_counting: True
paintings: paintings:
- id: arrows_painting_11 - id: arrows_painting_11
display_name: Painting
orientation: east orientation: east
req_blocked_when_no_doors: True # owl hallway in vanilla doors req_blocked_when_no_doors: True # owl hallway in vanilla doors
Courtyard: Courtyard:
@@ -2817,6 +2855,7 @@
panel: GREEN panel: GREEN
paintings: paintings:
- id: flower_painting_7 - id: flower_painting_7
display_name: Courtyard Painting
orientation: north orientation: north
Yellow Backside Area: Yellow Backside Area:
entrances: entrances:
@@ -2838,6 +2877,7 @@
door: Nines door: Nines
paintings: paintings:
- id: blueman_painting - id: blueman_painting
display_name: Near Nine Painting
orientation: east orientation: east
First Second Third Fourth: First Second Third Fourth:
# We are separating this door + its panels into its own room because they # We are separating this door + its panels into its own room because they
@@ -3173,6 +3213,7 @@
achievement: The Colorful achievement: The Colorful
paintings: paintings:
- id: arrows_painting_12 - id: arrows_painting_12
display_name: Painting
orientation: north orientation: north
progression: progression:
Progressive Colorful: Progressive Colorful:
@@ -3296,13 +3337,17 @@
- STRAYS - STRAYS
paintings: paintings:
- id: arrows_painting_8 - id: arrows_painting_8
display_name: Near Maze Painting
orientation: south orientation: south
- id: maze_painting_2 - id: maze_painting_2
display_name: Maze Side Middle Painting
orientation: north orientation: north
- id: owl_painting_2 - id: owl_painting_2
display_name: Orange Side Middle Painting
orientation: south orientation: south
required_when_no_doors: True required_when_no_doors: True
- id: clock_painting_4 - id: clock_painting_4
display_name: Near Orange Painting
orientation: north orientation: north
Outside The Initiated: Outside The Initiated:
entrances: entrances:
@@ -3490,8 +3535,10 @@
- OXEN - OXEN
paintings: paintings:
- id: clock_painting_5 - id: clock_painting_5
display_name: Brown Puzzles Painting
orientation: east orientation: east
- id: smile_painting_1 - id: smile_painting_1
display_name: Near Eight Painting
orientation: north orientation: north
sunwarps: sunwarps:
- dots: 3 - dots: 3
@@ -3866,8 +3913,10 @@
- BEGIN - BEGIN
paintings: paintings:
- id: pencil_painting2 - id: pencil_painting2
display_name: Near Bold Painting
orientation: west orientation: west
- id: north_missing2 - id: north_missing2
display_name: Directions Area Painting
orientation: north orientation: north
The Bold: The Bold:
entrances: entrances:
@@ -4189,12 +4238,14 @@
panel: FOUR panel: FOUR
paintings: paintings:
- id: maze_painting_3 - id: maze_painting_3
display_name: Near Four Painting
enter_only: True enter_only: True
orientation: north orientation: north
move: True move: True
required_door: required_door:
door: Green Painting door: Green Painting
- id: blueman_painting_2 - id: blueman_painting_2
display_name: Near Undeterred Painting
orientation: east orientation: east
sunwarps: sunwarps:
- dots: 4 - dots: 4
@@ -4557,6 +4608,7 @@
panel: NINE panel: NINE
paintings: paintings:
- id: smile_painting_5 - id: smile_painting_5
display_name: Near Eight Painting
enter_only: True enter_only: True
orientation: east orientation: east
required_door: required_door:
@@ -4742,10 +4794,13 @@
- LEARN - LEARN
paintings: paintings:
- id: smile_painting_7 - id: smile_painting_7
display_name: Near Turn/Return Painting
orientation: south orientation: south
- id: flower_painting_4 - id: flower_painting_4
display_name: Back Area Right Painting
orientation: south orientation: south
- id: pencil_painting3 - id: pencil_painting3
display_name: Back Area Left Painting
enter_only: True enter_only: True
orientation: east orientation: east
move: True move: True
@@ -4753,8 +4808,10 @@
room: Number Hunt room: Number Hunt
door: First Six door: First Six
- id: boxes_painting - id: boxes_painting
display_name: Near Directions Painting
orientation: south orientation: south
- id: cherry_painting - id: cherry_painting
display_name: Alcove Painting
orientation: east orientation: east
sunwarps: sunwarps:
- dots: 6 - dots: 6
@@ -4848,8 +4905,10 @@
- GREEN - GREEN
paintings: paintings:
- id: arrows_painting_7 - id: arrows_painting_7
display_name: Near Sunwarp Painting
orientation: east orientation: east
- id: fruitbowl_painting3 - id: fruitbowl_painting3
display_name: Hidden Painting
orientation: west orientation: west
enter_only: True enter_only: True
required_door: required_door:
@@ -4888,6 +4947,7 @@
tag: forbid tag: forbid
paintings: paintings:
- id: colors_painting - id: colors_painting
display_name: Painting
orientation: south orientation: south
The Bearer: The Bearer:
entrances: entrances:
@@ -5369,6 +5429,7 @@
panel: ANTECHAMBER panel: ANTECHAMBER
paintings: paintings:
- id: pencil_painting5 - id: pencil_painting5
display_name: Left Painting
orientation: south orientation: south
The Steady (Lemon): The Steady (Lemon):
entrances: entrances:
@@ -5391,6 +5452,7 @@
- MELON - MELON
paintings: paintings:
- id: pencil_painting4 - id: pencil_painting4
display_name: Right Painting
orientation: south orientation: south
The Steady (Topaz): The Steady (Topaz):
entrances: entrances:
@@ -6012,6 +6074,7 @@
panel: NIGHT panel: NIGHT
paintings: paintings:
- id: smile_painting_9 - id: smile_painting_9
display_name: Smiley Painting
orientation: north orientation: north
exit_only: True exit_only: True
The Artistic (Panda): The Artistic (Panda):
@@ -6124,6 +6187,7 @@
panel: BOWELS panel: BOWELS
paintings: paintings:
- id: panda_painting_3 - id: panda_painting_3
display_name: Panda Painting
exit_only: True exit_only: True
orientation: south orientation: south
required_when_no_doors: True required_when_no_doors: True
@@ -6235,6 +6299,7 @@
panel: THING panel: THING
paintings: paintings:
- id: boxes_painting2 - id: boxes_painting2
display_name: Lattice Painting
orientation: south orientation: south
exit_only: True exit_only: True
required_when_no_doors: True required_when_no_doors: True
@@ -6344,6 +6409,7 @@
panel: ROOT panel: ROOT
paintings: paintings:
- id: cherry_painting3 - id: cherry_painting3
display_name: Apple Painting
orientation: north orientation: north
exit_only: True exit_only: True
required_when_no_doors: True required_when_no_doors: True
@@ -6490,8 +6556,10 @@
- NEAR - NEAR
paintings: paintings:
- id: eye_painting_2 - id: eye_painting_2
display_name: Near Pillar Painting
orientation: west orientation: west
- id: smile_painting_2 - id: smile_painting_2
display_name: Near Window Painting
orientation: north orientation: north
Far Window: Far Window:
entrances: entrances:
@@ -6512,6 +6580,7 @@
door: Exit door: Exit
paintings: paintings:
- id: arrows_painting_5 - id: arrows_painting_5
display_name: Lobby Painting
orientation: east orientation: east
Outside The Wondrous: Outside The Wondrous:
entrances: entrances:
@@ -6562,9 +6631,11 @@
panel: SHRINK panel: SHRINK
paintings: paintings:
- id: symmetry_painting_a_1 - id: symmetry_painting_a_1
display_name: Doorknob Upper Painting
orientation: east orientation: east
exit_only: True exit_only: True
- id: symmetry_painting_b_1 - id: symmetry_painting_b_1
display_name: Doorknob Lower Painting
orientation: south orientation: south
The Wondrous (Bookcase): The Wondrous (Bookcase):
entrances: entrances:
@@ -6576,6 +6647,7 @@
tag: midblue tag: midblue
paintings: paintings:
- id: symmetry_painting_a_3 - id: symmetry_painting_a_3
display_name: Bookcase Painting
orientation: west orientation: west
exit_only: True exit_only: True
- id: symmetry_painting_b_3 - id: symmetry_painting_b_3
@@ -6590,6 +6662,7 @@
tag: midyellow tag: midyellow
paintings: paintings:
- id: symmetry_painting_a_5 - id: symmetry_painting_a_5
display_name: Chandelier Painting
orientation: east orientation: east
- id: symmetry_painting_b_5 - id: symmetry_painting_b_5
disable: True disable: True
@@ -6603,6 +6676,7 @@
tag: botbrown tag: botbrown
paintings: paintings:
- id: symmetry_painting_b_4 - id: symmetry_painting_b_4
display_name: Window Painting
orientation: north orientation: north
exit_only: True exit_only: True
- id: symmetry_painting_a_4 - id: symmetry_painting_a_4
@@ -6627,8 +6701,10 @@
tag: midyellow tag: midyellow
paintings: paintings:
- id: symmetry_painting_a_2 - id: symmetry_painting_a_2
display_name: Table Lower Painting
orientation: west orientation: west
- id: symmetry_painting_b_2 - id: symmetry_painting_b_2
display_name: Table Upper Painting
orientation: south orientation: south
exit_only: True exit_only: True
required: True required: True
@@ -6669,6 +6745,7 @@
- Achievement - Achievement
paintings: paintings:
- id: arrows_painting_9 - id: arrows_painting_9
display_name: Exit Painting
enter_only: True enter_only: True
orientation: south orientation: south
move: True move: True
@@ -6676,9 +6753,11 @@
door: Exit door: Exit
req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors
- id: symmetry_painting_a_6 - id: symmetry_painting_a_6
display_name: Fireplace Upper Painting
orientation: west orientation: west
exit_only: True exit_only: True
- id: symmetry_painting_b_6 - id: symmetry_painting_b_6
display_name: Fireplace Lower Painting
orientation: north orientation: north
req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors req_blocked_when_no_doors: True # the wondrous (table) in vanilla doors
Arrow Garden: Arrow Garden:
@@ -6700,6 +6779,7 @@
tag: midwhite tag: midwhite
paintings: paintings:
- id: flower_painting_6 - id: flower_painting_6
display_name: Painting
orientation: south orientation: south
Hallway Room (1): Hallway Room (1):
entrances: entrances:
@@ -6758,6 +6838,7 @@
- TOWER - TOWER
paintings: paintings:
- id: panda_painting - id: panda_painting
display_name: Painting
orientation: south orientation: south
progression: progression:
Progressive Hallway Room: Progressive Hallway Room:
@@ -6945,6 +7026,7 @@
tag: midwhite tag: midwhite
paintings: paintings:
- id: south_afar - id: south_afar
display_name: Painting
orientation: south orientation: south
Outside The Wanderer: Outside The Wanderer:
entrances: entrances:
@@ -7123,16 +7205,21 @@
panels: panels:
- ORDER - ORDER
paintings: paintings:
- id: smile_painting_3
orientation: west
- id: flower_painting_2 - id: flower_painting_2
display_name: Left Near Painting
orientation: east orientation: east
- id: scenery_painting_0a
orientation: north
- id: map_painting - id: map_painting
display_name: Left Far Painting
orientation: east orientation: east
- id: fruitbowl_painting4 - id: fruitbowl_painting4
display_name: Center Front Painting
orientation: south orientation: south
- id: scenery_painting_0a
display_name: Center Back Painting
orientation: north
- id: smile_painting_3
display_name: Right Far Painting
orientation: west
progression: progression:
Progressive Art Gallery: Progressive Art Gallery:
doors: doors:
@@ -7493,6 +7580,7 @@
panel: WORD panel: WORD
paintings: paintings:
- id: arrows_painting_3 - id: arrows_painting_3
display_name: Circle Painting
orientation: north orientation: north
Rhyme Room (Looped Square): Rhyme Room (Looped Square):
entrances: entrances:
@@ -7675,6 +7763,7 @@
- INNOVATIVE (Bottom) - INNOVATIVE (Bottom)
paintings: paintings:
- id: arrows_painting_4 - id: arrows_painting_4
display_name: Target Painting
orientation: north orientation: north
Room Room: Room Room:
# This is a bit of a weird room. You can't really get to it from the roof. # This is a bit of a weird room. You can't really get to it from the roof.
@@ -7944,8 +8033,10 @@
- CAT - CAT
paintings: paintings:
- id: arrows_painting_2 - id: arrows_painting_2
display_name: Left Painting
orientation: east orientation: east
- id: clock_painting_2 - id: clock_painting_2
display_name: Right Painting
orientation: east orientation: east
exit_only: True exit_only: True
required: True required: True
@@ -8022,6 +8113,7 @@
tag: midbrown tag: midbrown
paintings: paintings:
- id: clock_painting_3 - id: clock_painting_3
display_name: Painting
orientation: east orientation: east
req_blocked: True # outside the wise (with or without door shuffle) req_blocked: True # outside the wise (with or without door shuffle)
The Red: The Red:
@@ -8492,6 +8584,7 @@
- OPTICS - OPTICS
paintings: paintings:
- id: hi_solved_painting4 - id: hi_solved_painting4
display_name: Painting
orientation: south orientation: south
req_blocked_when_no_doors: True # owl hallway in vanilla doors req_blocked_when_no_doors: True # owl hallway in vanilla doors
Challenge Room: Challenge Room:

Binary file not shown.

View File

@@ -50,7 +50,7 @@ directives = Set["entrances", "panels", "doors", "panel_doors", "paintings", "su
panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt", "location_name"] panel_directives = Set["id", "required_room", "required_door", "required_panel", "colors", "check", "exclude_reduce", "tag", "link", "subtag", "achievement", "copy_to_sign", "non_counting", "hunt", "location_name"]
door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"] door_directives = Set["id", "painting_id", "panels", "item_name", "item_group", "location_name", "skip_location", "skip_item", "door_group", "include_reduce", "event", "warp_id"]
panel_door_directives = Set["panels", "item_name", "panel_group"] panel_door_directives = Set["panels", "item_name", "panel_group"]
painting_directives = Set["id", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"] painting_directives = Set["id", "display_name", "enter_only", "exit_only", "orientation", "required_door", "required", "required_when_no_doors", "move", "req_blocked", "req_blocked_when_no_doors"]
non_counting = 0 non_counting = 0
@@ -314,6 +314,10 @@ config.each do |room_name, room|
next next
end end
unless painting.include? "display_name" then
puts "#{room_name} - #{painting["id"] || "painting"} :::: Missing display name"
end
if painting.include?("orientation") then if painting.include?("orientation") then
unless ["north", "south", "east", "west"].include? painting["orientation"] then unless ["north", "south", "east", "west"].include? painting["orientation"] then
puts "#{room_name} - #{painting["id"] || "painting"} :::: Invalid orientation #{painting["orientation"]}" puts "#{room_name} - #{painting["id"] || "painting"} :::: Invalid orientation #{painting["orientation"]}"

View File

@@ -28,6 +28,7 @@ class PortalPlando(PlandoConnections):
- entrance: Searing Crags - entrance: Searing Crags
exit: Glacial Peak Portal exit: Glacial Peak Portal
""" """
display_name = "Portal Plando Connections"
portals = [f"{portal} Portal" for portal in PORTALS] portals = [f"{portal} Portal" for portal in PORTALS]
shop_points = [point for points in SHOP_POINTS.values() for point in points] shop_points = [point for points in SHOP_POINTS.values() for point in points]
checkpoints = [point for points in CHECKPOINTS.values() for point in points] checkpoints = [point for points in CHECKPOINTS.values() for point in points]
@@ -48,6 +49,7 @@ class TransitionPlando(PlandoConnections):
exit: Dark Cave - Right exit: Dark Cave - Right
direction: both direction: both
""" """
display_name = "Transition Plando Connections"
entrances = frozenset(RANDOMIZED_CONNECTIONS.keys()) entrances = frozenset(RANDOMIZED_CONNECTIONS.keys())
exits = frozenset(RANDOMIZED_CONNECTIONS.values()) exits = frozenset(RANDOMIZED_CONNECTIONS.values())

View File

@@ -32,7 +32,7 @@ class MessengerRules:
self.connection_rules = { self.connection_rules = {
# from ToTHQ # from ToTHQ
"Artificer's Portal": "Artificer's Portal":
lambda state: state.has_all({"Demon King Crown", "Magic Firefly"}, self.player), lambda state: state.has("Demon King Crown", self.player),
"Shrink Down": "Shrink Down":
lambda state: state.has_all(NOTES, self.player), lambda state: state.has_all(NOTES, self.player),
# the shop # the shop
@@ -267,6 +267,8 @@ class MessengerRules:
# tower of time # tower of time
"Tower of Time Seal - Time Waster": "Tower of Time Seal - Time Waster":
self.has_dart, self.has_dart,
# corrupted future
"Corrupted Future - Key of Courage": lambda state: state.has("Magic Firefly", self.player),
# cloud ruins # cloud ruins
"Time Warp Mega Shard": "Time Warp Mega Shard":
lambda state: self.has_vertical(state) or self.can_dboost(state), lambda state: self.has_vertical(state) or self.can_dboost(state),
@@ -370,7 +372,7 @@ class MessengerRules:
add_rule(multiworld.get_entrance("Shrink Down", self.player), self.has_dart) add_rule(multiworld.get_entrance("Shrink Down", self.player), self.has_dart)
multiworld.completion_condition[self.player] = lambda state: state.has("Do the Thing!", self.player) multiworld.completion_condition[self.player] = lambda state: state.has("Do the Thing!", self.player)
if self.world.options.accessibility: # not locations accessibility if self.world.options.accessibility: # not locations accessibility
set_self_locking_items(self.world, self.player) set_self_locking_items(self.world)
class MessengerHardRules(MessengerRules): class MessengerHardRules(MessengerRules):
@@ -530,9 +532,11 @@ class MessengerOOBRules(MessengerRules):
self.world.options.accessibility.value = MessengerAccessibility.option_minimal self.world.options.accessibility.value = MessengerAccessibility.option_minimal
def set_self_locking_items(world: "MessengerWorld", player: int) -> None: def set_self_locking_items(world: "MessengerWorld") -> None:
# locations where these placements are always valid # locations where these placements are always valid
allow_self_locking_items(world.get_location("Searing Crags - Key of Strength").parent_region, "Power Thistle") allow_self_locking_items(world.get_location("Searing Crags - Key of Strength").parent_region, "Power Thistle")
allow_self_locking_items(world.get_location("Sunken Shrine - Key of Love"), "Sun Crest", "Moon Crest") allow_self_locking_items(world.get_location("Sunken Shrine - Key of Love"), "Sun Crest", "Moon Crest")
allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage").parent_region, "Demon King Crown")
allow_self_locking_items(world.get_location("Elemental Skylands Seal - Water"), "Currents Master") allow_self_locking_items(world.get_location("Elemental Skylands Seal - Water"), "Currents Master")
if not world.options.shuffle_transitions:
allow_self_locking_items(world.get_location("Corrupted Future - Key of Courage").parent_region,
"Demon King Crown")

View File

@@ -1 +1 @@
requests >= 2.28.1 # used by client requests >= 2.28.1 # used by client

View File

Binary file not shown.

View File

View File

@@ -52,11 +52,13 @@ class MuseDashCollections:
"Nyaa SFX Trap": STARTING_CODE + 8, "Nyaa SFX Trap": STARTING_CODE + 8,
"Error SFX Trap": STARTING_CODE + 9, "Error SFX Trap": STARTING_CODE + 9,
"Focus Line Trap": STARTING_CODE + 10, "Focus Line Trap": STARTING_CODE + 10,
"Beefcake SFX Trap": STARTING_CODE + 11,
} }
sfx_trap_items: List[str] = [ sfx_trap_items: List[str] = [
"Nyaa SFX Trap", "Nyaa SFX Trap",
"Error SFX Trap", "Error SFX Trap",
"Beefcake SFX Trap",
] ]
filler_items: Dict[str, int] = { filler_items: Dict[str, int] = {

View File

@@ -627,4 +627,10 @@ SONG_DATA: Dict[str, SongData] = {
"Sharp Bubbles": SongData(2900751, "83-3", "Cosmic Radio 2024", True, 7, 9, 11), "Sharp Bubbles": SongData(2900751, "83-3", "Cosmic Radio 2024", True, 7, 9, 11),
"Replay": SongData(2900752, "83-4", "Cosmic Radio 2024", True, 5, 7, 9), "Replay": SongData(2900752, "83-4", "Cosmic Radio 2024", True, 5, 7, 9),
"Cosmic Dusty Girl": SongData(2900753, "83-5", "Cosmic Radio 2024", True, 5, 7, 9), "Cosmic Dusty Girl": SongData(2900753, "83-5", "Cosmic Radio 2024", True, 5, 7, 9),
"Meow Rock feat. Chun Ge, Yuan Shen": SongData(2900754, "84-0", "Muse Dash Legend", True, None, None, None),
"Even if you make an old radio song with AI": SongData(2900755, "84-1", "Muse Dash Legend", False, 3, 6, 8),
"Unusual Sketchbook": SongData(2900756, "84-2", "Muse Dash Legend", True, 6, 8, 11),
"TransientTears": SongData(2900757, "84-3", "Muse Dash Legend", True, 6, 8, 11),
"SHOOTING*STAR": SongData(2900758, "84-4", "Muse Dash Legend", False, 5, 7, 9),
"But the Blue Bird is Already Dead": SongData(2900759, "84-5", "Muse Dash Legend", False, 6, 8, 10),
} }

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